如果我不保留IBOutlet会怎么样?

如果我这样做:

@interface RegisterController : UIViewController <UITextFieldDelegate> { IBOutlet UITextField *usernameField; } 

而不是这个:

 @interface RegisterController : UIViewController <UITextFieldDelegate> { UITextField *usernameField; } @property (nonatomic, retain) IBOutlet UITextField *usernameField; 

会发生什么事情吗? 我知道在第二种情况下,领域是保留,但是这是否因为笔尖拥有领域而有所不同? 没有保留,场地会消失吗? 在什么情况下? 在第一个案例中的代码工作,想知道这是否是一个问题在内存pipe理方面。

为了清晰和一致,build议您为所有IBOutlet声明属性。 详细内容在“ 内存pipe理编程指南 ”中有详细说明。 基本的要点是,当你的NIB对象被解除存档时,nib加载代码将通过setValue:forKey:来设置所有IBOutlet。 当你在属性上声明内存pipe理行为时,发生什么事情是没有什么神秘的。 如果视图被卸载,但是您使用了一个声明为retain的属性,那么您仍然可以获得对您的文本字段的有效引用。

也许一个更具体的例子将有助于指出为什么你应该使用一个保留属性:

我将对你正在工作的上下文做一些假设 – 我将假设上面的UITextField是由UIViewController控制的另一个视图的子视图。 我将假定在某个时候,视图不在屏幕上(也许它是在UINavigationController的上下文中使用的),并且在某个时候你的应用程序会得到一个内存警告。

所以可以说你的UIViewController子类需要访问它的视图才能在屏幕上显示它。 此时,将加载nib文件,并使用setValue:forKey:由nib加载代码设置每个IBOutlet属性。 这里要注意的重要的是顶级视图,将被设置为UIViewController的视图属性(将保留这个顶级视图)和你的UITextField,这也将被保留。 如果只是简单地设置,它将有一个保留放在它的笔尖加载代码,否则该属性将保留它。 UITextField也是顶级UIView的一个子视图,所以它将有一个额外的保留在顶层视图的子视图数组中,所以此时文本字段被保留了两次。

此时,如果您想以编程方式切换文本字段,则可以这样做。 使用这个属性使得内存pipe理更加清晰。 你只需设置一个新的自动发布的文本字段的属性。 如果你没有使用这个属性,你必须记得释放它,并select保留新的属性。 在这一点上,谁拥有这个新的文本字段是有些含糊不清的,因为内存pipe理语义不包含在setter中。

现在我们假设一个不同的视图控制器被推入UINavigation Controller的堆栈,所以这个视图不再处于前台。 在存储器警告的情况下,该离屏视图控制器的视图将被卸载。 此时,顶层UIView的视图属性将被清除,它将被释放和释放。

因为UITextField被设置为保留的属性,所以UITextField不会被释放,因为它只能保留为顶层视图的子视图数组。

如果UITextField的实例variables不是通过一个属性设置的话,那么它也会在附近,因为在设置实例variables时,nib加载代码保留了它。

这里强调的一个有趣的地方在于,由于UITextField是通过属性额外保留的,所以在内存警告的情况下,您可能不想保留它。 出于这个原因,你应该清空 – [UIViewController viewDidUnload]方法中的属性。 这将摆脱UITextField的最终版本,并按预期释放它。 如果使用该属性,则必须记得明确地释放它。 虽然这两个行动在function上是等同的,但意图是不同的。

如果不是交换文本字段,而是select将其从视图中删除,则可能已将其从视图层次结构中删除,并将该属性设置为零,或者释放文本字段。 虽然在这种情况下可以编写正确的程序,但是在viewDidUnload方法中容易造成过度释放文本字段的错误。 过度释放对象是导致崩溃的错误; 设置一个已经为零的属性为零是不是。

我的描述可能过于冗长,但我不想在该场景中遗漏任何细节。 只要遵循指导原则,就可以避免在遇到更复杂的情况时出现问题。

另外值得注意的是,桌面上的Mac OS X上的内存pipe理行为有所不同。 在桌面上,在没有setter的情况下设置IBOutlet不会保留实例variables; 但如果可用的话,再次使用setter。

从内存pipe理的angular度来说,声明IBOutlet什么都不做(IBOutlet从字面上看就是#defined)。 将IBOutlet包含在声明中的唯一原因是,如果您打算在Interface Builder中连接它(这是IBOutlet声明的含义,提示IB)。

现在,为一个实例variables创build@property的唯一原因是如果你打算以编程方式分配它们。 如果你不这样做(也就是说,你只是在IB中设置你的用户界面),你是否做一个财产并不重要。 没有理由,国际海事组织。

回到你的问题。 如果你只在IB中设置这个ivar(usernameField),不要打扰这个属性,它不会影响任何东西。 如果你为usernameField创build一个属性(因为你正在编程创build它),那么肯定会为它创build一个属性,并且如果是的话,绝对保留属性。

其实有两种模式:

老模式

这些模型是Objective-C 2.0之前的模型,是从Mac OS Xinheritance而来的。它仍然有效,但是不应该声明属性来修改ivars。 那是:

 @interface StrokeWidthController : UIViewController { IBOutlet UISlider* slider; IBOutlet UILabel* label; IBOutlet StrokeDemoView* strokeDemoView; CGFloat strokeWidth; } @property (assign, nonatomic) CGFloat strokeWidth; - (IBAction)takeIntValueFrom:(id)sender; @end 

在这个模型中,你不保留IBOutlet ivars,但你必须释放它们。 那是:

 - (void)dealloc { [slider release]; [label release]; [strokeDemoView release]; [super dealloc]; } 

新模式

您必须声明IBOutletvariables的属性:

 @interface StrokeWidthController : UIViewController { IBOutlet UISlider* slider; IBOutlet UILabel* label; IBOutlet StrokeDemoView* strokeDemoView; CGFloat strokeWidth; } @property (retain, nonatomic) UISlider* slider; @property (retain, nonatomic) UILabel* label; @property (retain, nonatomic) StrokeDemoView* strokeDemoView; @property (assign, nonatomic) CGFloat strokeWidth; - (IBAction)takeIntValueFrom:(id)sender; @end 

另外你必须在dealloc中释放variables:

 - (void)dealloc { self.slider = nil; self.label = nil; self.strokeDemoView = nil; [super dealloc]; } 

进一步模式,在非脆弱的平台上,您可以删除ivars:

 @interface StrokeWidthController : UIViewController { CGFloat strokeWidth; } @property (retain, nonatomic) IBOutlet UISlider* slider; @property (retain, nonatomic) IBOutlet UILabel* label; @property (retain, nonatomic) IBOutlet StrokeDemoView* strokeDemoView; @property (assign, nonatomic) CGFloat strokeWidth; - (IBAction)takeIntValueFrom:(id)sender; @end 

这个怪物

在这两种情况下,通过调用setValue:forKey:来设置sockets。 内部运行时(特别是_decodeObjectBinary)检查setter方法是否存在。 如果它不存在(只有伊娃存在),它发送一个额外的保留伊娃。 为此,如果没有setter方法,则不应保留IBOutlet。

这两个接口定义的工作方式没有任何区别,直到您开始使用属性提供的访问器。

在这两种情况下,您仍然需要在dealloc或viewDidUnload方法中释放并设置IBOutlet。

IBOutlet指向一个在XIB文件中实例化的对象。 该对象由XIB文件的File's Owner对象(通常是IBOutlet声明的视图控制器)拥有。

由于该对象是作为加载XIB的结果而创build的,因此它的保留计数为1,并由文件所有者拥有,如上所述。 这意味着文件的所有者负责在释放它时释放它。

使用retain属性添加属性声明只需指定setter方法应该保留传入的对象以进行设置 – 这是正确的方法。 如果您没有在属性声明中指定保留,那么IBOutlet可能会指向一个可能不再存在的对象,因为它被所有者释放,或者在程序生命周期的某个点自动释放。 保留它可以防止该对象被释放,直到完成它。

创buildnib文件中的对象的保留计数为1,然后自动释放。 在重build对象层次结构时,如果没有setter方法可用,UIKit使用setValue:forKey:重新build立对象之间的连接,该对象使用可用的setter方法或默认保留该对象。 这意味着你有一个sockets的任何物体仍然有效。 但是,如果存在任何不存储在sockets中的顶级对象,则必须保留由loadNibNamed:owner:options:方法返回的数组或数组中的对象,以防止这些对象被过早释放。

那么,在第二种情况下,你正在为特定的IBOutlet添加一个getter / setter方法。 任何时候你添加一个getter / setter方法,你(几乎总是)想把它设置为保留内存pipe理问题。 我认为一个更好的方式来提出你的问题会是这样的:

 @interface RegisterController : UIViewController <UITextFieldDelegate> { IBOutlet UITextField *usernameField; } @property (nonatomic) IBOutlet UITextField *usernameField; 

要么

 @interface RegisterController : UIViewController <UITextFieldDelegate> { IBOutlet UITextField *usernameField; } @property (nonatomic, retain) IBOutlet UITextField *usernameField; 

在那种情况下,是的,你需要添加一个保留,因为它会影响内存pipe理。 即使它可能没有任何效果,如果您正在编程添加和删除IBOutlet的,您可能会遇到问题。

作为一般规则:每当你有一个IBOutlet时,总是添加一个@property(带有retain)。