IBOutlet需要成为一个属性并且被合成吗?

53

在大多数示例中,我看到以下的IBOutlets设置:



(Example A)

FooController.h:

@interface FooController : UIViewController {
    UILabel *fooLabel;
}

@property (nonatomic, retain) IBOutlet UILabel *fooLabel;

@end

FooController.m:

@implementation FooController

@synthesize fooLabel;

@end

但是这个也可以正常工作(注意:没有属性和synthesize):



(Example B)

FooController.h:

@interface FooController : UIViewController {
    IBOutlet UILabel *fooLabel;
}

@end

FooController.m:

@implementation FooController

@end

像示例B中定义IBOutlets有什么缺点吗?比如内存泄漏?看起来运行良好,我更喜欢不将IBOutlets作为公共属性公开,因为它们并没有被用作这样,它们只在控制器实现中使用。在没有真正需要的情况下在三个位置定义它并不让我感到很DRY(不要重复自己)。

4个回答

96
在Mac OS X上,IBOutlets的连接方式如下所示:
  1. 查找名为set<OutletName>:的方法。如果存在,则调用它。
  2. 如果不存在该方法,则查找名为<OutletName>的实例变量,并设置它,但不保留它。
在iPhone OS上,IBOutlets的连接方式如下所示:
  1. 调用[object setValue:outletValue forKey:@"<OutletName>"]
setValue forKey的行为类似于以下操作:
  1. 查找名为set<OutletName>:的方法。如果存在,则调用它。
  2. 如果不存在该方法,则查找名为<OutletName>的实例变量,并设置并保留它。
如果您使用一个属性,则在两个平台上都会落入“查找名为set<OutletName>:...”的情况中。如果只是使用实例变量,则在Mac OS X VS iPhone OS上将具有不同的保留/释放行为。使用实例变量没有问题,只需要在切换平台时处理这种行为差异即可。
这里是有关此主题的完整文档链接。 https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/LoadingResources/CocoaNibs/CocoaNibs.html#//apple_ref/doc/uid/10000051i-CH4-SW6

如果变量名与属性名不同会发生什么?它们不同会有影响吗? - David.Chu.ca
上面的“OutletName”名称被定义为源代码中“IBOutlet”关键字旁边的任何内容。如果在@property中有IBOutlet,则实例变量的名称并不重要,因为会找到一个setter。如果由于某种原因不存在setter,则连接输出时会引发异常。如果IBOutlet关键字位于实例变量上,并且存在一个名称不匹配的setter,则不会调用该setter。 - Jon Hess
当使用ARC时,https://dev59.com/mGPVa4cB1Zd3GeqP2Re6?rq=1建议在Mac上也明确使用弱引用。 - codingFriend1
链接已失效。 - Sedat Kapanoglu

12
在 Mac OS X 上,IBOutlets 默认情况下不会被保留。这与 iPhone OS 的行为相反:在 iPhone OS 上,如果您没有声明一个属性,则该属性将被保留,您必须在 dealloc 方法中释放此属性。此外,64 位运行时可以使用属性声明来合成实例变量。这意味着,某一天实例变量(带有 IBOutlet)可能会被省略。
出于这些原因,始终创建一个属性并仅在属性中使用 IBOutlet 更加一致和兼容。不幸的是,这也更冗长。
在第一个示例中,您必须始终在 dealloc 方法中释放 outlet。在第二个示例中,只需要在 iPhone OS 中释放 outlet。

4
最终结果完全相同,但需要记住以下几点:
- 当使用实例字段作为outlets时,在dealloc中不应该释放它们。 - 当使用具有(retain)属性的属性时,必须在dealloc中释放该属性(使用self.property=nil或释放支持变量)。这使得情况更加透明。 - 实际上,一切都归结为同样的规则:“你应该释放你分配/保留的东西”。因此,如果您将实例字段用作outlet,则没有分配/保留它,因此不应该释放它。

5
这个建议适用于Mac OS X,但不适用于iPhone OS。请看下面我的回答。 - Jon Hess
1
在dealloc方法中调用"self.property=nil"不是一个好的实践。如果你的子类化了,那么在init或dealloc中不应该调用方法,因为你的子类可能没有预期这些setter在它被dealloc之后或在它被初始化之前被调用。 - Jon Hess
这是释放使用实例变量合成的自动保留属性的唯一方法(没有任何明确声明支持字段)。无论好坏,你都别无选择。 - Philippe Leybaert
1
如果您不使用垃圾回收,那么您可以选择声明一个支持实例变量。 - Jon Hess
2
这就是整个重点:当您使用实例变量合成时,没有后备实例变量。因此,如果您选择使用自动实例变量合成,则释放它的唯一方法是将属性设置为nil。 - Philippe Leybaert
那不应该是“你应该释放你分配/保留的东西”吗? - svth

1

可能那些示例使用retain是因为示例代码在程序中动态分配和初始化了UILabel,然后将其添加到UIView中。这是许多示例的情况,因为学习如何使用Interface Builder通常不是它们的重点。

第二个示例(无属性和无synthesize),使用IBOutlet时,开发人员通过将IBOulet拖动到Label或其他View组件上来“分配”UILabel(Button、View等)。在我看来,前面的拖放操作(将Label放到View上)也会将子视图Label添加到View中,以此类推。Label由View保留;View由Window保留;Window由File's Owner保留。 File's Owner通常是您在main中引导的文档。

您会注意到,当您通过添加awakeFromNib来步进程序时

- (void)awakeFromNib
{
    [fooLabel blahblah];
}

fooLabel已经有了一个内存地址。

这是因为该标签是通过文件包(nib文件)使用initWithCoder而不是init进行初始化的。这本质上是将文件流反序列化为对象,然后设置IBOutlet变量。(我们仍在谈论IBOutlet方法)。

还要注意,前面提到的iOS方法使用了键值方法。

  call [object setValue:outletValue forKey:@"<OutletName>"]

这是观察者/可观察模式。该模式要求可观察对象在Set/Array中引用每个观察者。值的更改将迭代Set/Array并同样更新所有观察者。该Set已经会保留每个观察者,因此在iOS中不需要保留。

进一步的内容只是推测。

看起来当您使用Interface Builder时,

 @property (nonatomic, retain) IBOutlet UILabel *fooLabel;

应该可能被更改为。
@property (nonatomic, weak) IBOutlet UILabel *fooLabel;

或者

@property (nonatomic, assign) IBOutlet UILabel *fooLabel;

然后它不需要在dealloc方法中释放。此外,它将满足OSX和iOS的要求。

这是基于逻辑推断的,我可能会漏掉一些细节。

尽管如此,如果视图在程序的整个生命周期内都是持久的,那么这可能并不重要。而在模态对话框中的标签(打开、关闭、打开、关闭)每个周期可能会过度保留并泄漏。这是因为(再次推测),每个关闭的对话框都被序列化到文件系统中,因此保留了x、y位置和大小以及其子视图等,并在下一个会话打开时进行反序列化...与最小化或隐藏相反。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接