如何提示编译器使用 UIView 子类

Posted

技术标签:

【中文标题】如何提示编译器使用 UIView 子类【英文标题】:How to hint use of UIView subclass for compiler 【发布时间】:2010-11-23 09:27:31 【问题描述】:

我有一个 UIViewController 的子类,它负责单个 UIWebView。

由于这是一个简单的案例,我覆盖-(void)loadView,实例化 UIWebView 并将其分配给控制器的view 属性:

- (void)loadView 

    UIWebView *wv = [[[UIWebView alloc] initWithFrame:self.frame] autorelease];
    // other configuration here...  
    self.view = wv;

这很好,直到我调用 UIWebView 的方法。比如……

[self.view loadhtmlString:HTMLString baseURL:baseURL];

...导致编译器警告...

warning: 'UIView' may not respond to '-loadHTMLString:baseURL:'

...因为view 属性被声明为UIView

现在可以通过强制转换轻松解决警告...

[(UIWebView *)self.view loadHTMLString:HTMLString baseURL:baseURL];

...但我想做的是在界面中提供正确的类型提示。我尝试覆盖MyViewController.h 中的view 属性,但这也让编译器感到不安:

warning: property 'view' type does not match super class 'UIViewController' property type

有什么方法可以告诉编译器(和我的同事)这就是我正在做的事情,并且我知道这就是我正在做的事情并且一切都好? (如果不是,我想我会坚持演员阵容。)

TIA

编辑:我尝试按照 marcus.ramsden 的回答重新声明视图属性:这消除了警告(以及对演员表的需要),但完全停止了视图的出现!我不确定为什么会这样,因为控制器在被要求时仍会返回 UIView(子类)...

【问题讨论】:

为什么不添加UIWebView实例作为子视图? @NR4TR 两个原因:额外的 UIView 除了复杂性之外什么都不会增加,而且我想要一个额外的 @property 来保存对 Web 视图的引用。直接使用 UIWebView 并在过程中重用 view 属性就更干净了。 (我需要的只是一种告诉编译器的方法,这样我就可以取消强制转换了。) 【参考方案1】:

简短的回答是没有合理的方法可以做到这一点。找到一种不合理的方法来做这件事会产生比它解决的更多的问题。我知道这不是您想要的答案,但为了将来阅读此问题的任何人的利益,我将解释为什么我认为您不应该这样做。

view 属性是 UIViewController 的公共接口的一部分,返回值的类型是该接口的一部分。更改您子类化的类的公共接口是很臭的。如果这对您来说还不够,请考虑视图控制器的视图属性是 UIKit 的基础部分;几乎没有什么你可以搞砸的事情可能会对框架产生更系统的影响。谁知道在那个简单的接口吸气剂的外表下隐藏着什么样的黑暗阴谋?基于一些已经离开这里的 cmets,重新定义这个属性似乎会以明显的方式破坏事物;我会远离。

如果您来自 ruby​​ 之类的语言,那么您无法在没有编译器警告的情况下向视图属性返回的对象发送任意消息这一事实可能看起来并不令人满意。然而,无论好坏,Objective C 是一种静态(如果不是特别强)类型的语言。当您使用特定工具时,值得尝试利用该工具的优势并避免其劣势,而不是试图将其硬塞进另一个工具的习语中。

另一方面,如果你来自像 C++ 这样的强类型语言,那么这种转换也会让人感到非常不满意。强制转换颠覆了静态类型语言严重依赖的类型系统,并且几乎总是令人讨厌的代码气味。 C-style casts是C-type系统的核武器;它们绝对会覆盖所有内容,而且它们几乎总是用错了。

如果您将来决定要在视图中与 Web 视图一起添加标签,该怎么办?在这种情况下,您需要将 Web 视图移动为子视图;但是,如果您将视图控制器的基本视图投射到任何地方的 UIWebView,那么您的演员现在是谎言。遗憾的是,编译器不会帮助你找到那些谎言,因为 C 风格的强制转换是未经检查的。您可以使用 UIView、int、块、函数指针或 rhinocerous 制作 UIWebView,编译器不会进行窥视。但是,您的应用会严重崩溃。

最后,您说您有兴趣避免复杂性。考虑将您的 Web 视图添加为基本视图的子视图只需要您声明(并合成)单个属性并在 viewDidLoad 中添加子视图。考虑到这一点,所有对 Web 视图的调用都如下所示:

[self.webView loadHTMLString:HTMLString baseURL:baseURL];

或者,如果您坚持投射,对您的网络视图的每次调用将如下所示:

[(UIWebView *)self.view loadHTMLString:HTMLString baseURL:baseURL];

对我来说,前者似乎不那么复杂,也不那么脆弱。

【讨论】:

我来这里是为了说这个。在我们的具有专用 WebController(UIViewController 的子视图)类的应用程序中,我们将 UIWebView 添加到 self.view 并在 WebController 界面中为 Web 视图创建一个属性。可以肯定,类型转换有其一席之地,但在这种情况下,真正优雅的解决方案更倾向于使用更清晰、非骇客的代码,而不是担心额外的容器视图的开销(它是如此之小以至于微不足道)。 感谢您的回答和解释。这一切都说得通。 @par:感谢您对这个问题的 cmets,他们非常有帮助。【参考方案2】:

由于它是一个子类,您可以将view 属性/方法覆盖为UIWebView。由于 UIWebViewUIView 的子类,并且您是子类的唯一用户,因此这应该是完全安全的。

@interface MyAbstractViewController : UIViewController 
    UIWebView *_webView;

@property (nonatomic, retain) UIWebView *view;
@end

@implementation MyAbstractViewController    
@dynamic view;
- (void)setView:(UIWebView *)webView 
  [super setView:webView];
  _webView = webView;


- (UIWebView *)view 
  return _webView;

@end

【讨论】:

这是我最初的目标。我尝试覆盖view 属性,但最终得到了我的问题中提到的第二种类型的警告——与我的尝试的两个不同之处是ivar 的命名和@dynamic 的使用;我会同意但亚当·米利根的回答,但我会尝试你的建议。一直在学习。谢谢! 这里有@dynamic,实际上不需要你提供一个实现。您只需重新声明该属性以表示 UIWebView*,只要您知道自己在做什么,编译器就不会抱怨。【参考方案3】:

你可以定义一个属性

@property (nonatomic, readonly) UIWebView *webView;

然后这样做:

- (UIWebView*)webView 
   return (UIWebView*)self.view;


- (void)loadView 

    UIWebView *wv = [[[UIWebView alloc] initWithFrame:self.frame] autorelease];
    // other configuration here...  
    self.view = wv;

然后您可以随后通过self.webView 访问您的WebView,无需任何转换。本质上,您将演员集中到代码中的一个特定位置。如果您想插入另一个视图(如标签),您只需修改代码中的一个位置。

【讨论】:

好主意。对于我现有的代码,这可能是最好的解决方案。谢谢。【参考方案4】:

从文档看来,可以通过使用类别来做你想做的事。例如;

// UIWebViewController.h
@interface UIWebViewController : UIViewController 



@end

@interface UIWebViewController ()

@property (nonatomic,retain) UIWebView *view;

@end

// UIViewController.m
@implementation UIWebViewController

@synthesize view;

- (void)viewDidLoad 
    UIWebView *wv = [[UIWebView alloc] initWithFrame:webViewFrame];
    self.view = wv;
    [self.view loadHTML:@"<h1>Hello World</h1>" baseURL:baseURL];
    [wv release];


@end

在声明的属性 > 使用属性 > 属性重新声明中的 http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjectiveC/Articles/ocProperties.html 中有一些关于此的内容。

试过了,我没有收到任何编译器投诉。

【讨论】:

啊!事实证明这不太有效:重新声明视图属性会导致视图不显示。我不知道为什么它没有显示(还)但是,我宁愿有一个编译器警告和一个视图,而不是根本没有视图! :) 嗯,刚刚构建然后运行,而不是仅仅构建它,它似乎杀死了一些东西。我想知道这是否会进一步破坏 UIKit 链。如果它不能完全起作用,可能会收回这个答案。 当你重写了 UIViewController 的视图访问器方法时,为什么你仍然希望它能够工作?【参考方案5】:

将值转换或添加一个新的 UIWebView 实例变量到您的 viewController。

UIViewController 的 view 方法的工作不是告诉你的伙伴你使用了什么样的子类。

如果你能做你想做的事,你只需要在 UIKit 期望 UIViewController 的视图方法返回 UIView 的所有地方都转换回 UIView。

【讨论】:

【参考方案6】:

如前所述,虽然这样做是可能,但可能明智

您可以在 UIView 本身上声明一个类别,表明它可能会响应您尝试调用的选择器。这可以在自定义视图控制器的实现文件中完成。它看起来像这样:

@interface UIView (WebViewFakery)

- (void)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL

@end

您必须在view 上添加您尝试调用的所有 UIWebView 方法。也许不是最优雅的解决方案,但需要考虑另一种解决方案。

【讨论】:

如果你这样做,你会得到一个编译器警告 UIView loadHTMLString:baseURL: 是未实现的,最初的问题是消除编译器警告并增加优雅。您回答问题的精神,也就是说,试图对 OP 的问题提出创造性的解决方案并有所帮助,不会错过,但这会导致代码完全无法理解。在阅读别人的代码时,您应该能够假设类别方法将对类别类型的所有类执行某些操作。这只会造成混乱。 实际上,这构建得很好,除了方法签名末尾缺少分号。它确实抑制了编译器警告。

以上是关于如何提示编译器使用 UIView 子类的主要内容,如果未能解决你的问题,请参考以下文章

子类化的 UIView,当设置为 Storyboard 中的默认视图时,编译器无法识别而不进行强制转换

快速覆盖 UIView 中的默认图层类型

带有协议问题的 Swift Generic UIView 子类

java(面向对象)中,子类如何调用父类的构造方法?分别从无参和有参角度

我正在使用 drawRect 实现 UIView 子类:如何绘制按钮?

如何在界面构建器中使用框架中的 UIView 子类