如果我这样做:
@interface RegisterController : UIViewController <UITextFieldDelegate>
{
IBOutlet UITextField *usernameField;
}
而不是:
@interface RegisterController : UIViewController <UITextFieldDelegate>
{
UITextField *usernameField;
}
@property (nonatomic, retain) IBOutlet UITextField *usernameField;
会发生什么不好的事吗?我知道在第二种情况下,该字段被保留,但是由于笔尖拥有该字段,这是否会有所不同?没有保留,这个领域会消失吗?在什么情况下?第一种情况下的代码工作,想知道这是否是内存管理方面的问题。
答案 0 :(得分:71)
为了清晰和一致,建议您为所有IBOutlet声明属性。 详细信息在Memory Management Programming Guide中详细说明。基本要点是,当您的NIB对象被取消归档时,nib加载代码将通过setValue:forKey:来设置所有IBOutlet。当您在属性上声明内存管理行为时,对于发生的事情并不神秘。如果视图被卸载,但您使用了一个声明为retain的属性,那么您仍然可以获得对文本字段的有效引用。
或许更具体的例子可以说明你应该使用保留属性的原因:
我将对你正在工作的上下文做一些假设 - 我假设上面的UITextField是另一个由UIViewController控制的视图的子视图。我将假设在某些时候,视图不在屏幕上(可能是在UINavigationController的上下文中使用),并且在某些时候你的应用程序会收到内存警告。
因此,假设您的UIViewController子类需要访问其视图以在屏幕上显示它。 此时,将加载nib文件,并使用setValue:forKey:由nib加载代码设置每个IBOutlet属性。这里要注意的重要事项是将设置为UIViewController的view属性的顶级视图(将保留此顶级视图)和UITextField,它们也将被保留。如果只是设置它,它将通过nib加载代码保留它,否则属性将保留它。 UITextField也将是顶级UIView的子视图,因此它将在顶层视图的子视图数组中有一个额外的保留,所以此时文本字段已被保留两次。
此时,如果您想以编程方式切换文本字段,则可以这样做。使用该属性可以使内存管理更清晰;您只需使用新的自动释放文本字段设置该属性。如果您没有使用该属性,则必须记住将其释放,并可选择保留新属性。在这一点上,对于拥有这个新文本字段的人来说有点含糊不清,因为内存管理语义不包含在setter中。
现在假设在UINavigation Controller的堆栈上推送了一个不同的视图控制器,因此该视图不再位于前台。在内存警告的情况下,将卸载此屏幕外视图控制器的视图。此时,顶级UIView的view属性将被清除,它将被释放并取消分配。
因为UITextField被设置为保留的属性,所以不会释放UITextField,因为它只保留了顶级视图的子视图数组。
如果没有通过属性设置UITextField的实例变量,它也会出现,因为在设置实例变量时,nib加载代码保留了它。
有一点值得注意的是,因为UITextField还通过属性保留,所以在发生内存警告时,您可能不希望保留它。因此,您应该在 - [UIViewController viewDidUnload]方法中清除属性。这将取消UITextField上的最终版本并按预期取消分配。如果使用该属性,则必须记住明确释放它。虽然这两个动作在功能上是等价的,但意图却不同。
如果不是交换文本字段,而是选择将其从视图中删除,您可能已将其从视图层次结构中删除并将属性设置为nil,或者释放文本字段。虽然在这种情况下可以编写正确的程序,但很容易在viewDidUnload方法中产生过度释放文本字段的错误。过度释放对象是导致崩溃的错误;将已经为零的属性设置为nil不是。
我的描述可能过于冗长,但我不想在方案中遗漏任何细节。简单地遵循这些指导原则有助于在遇到更复杂的情况时避免出现问题。
另外值得注意的是,桌面上的Mac OS X上的内存管理行为有所不同。在桌面上,在没有setter的情况下设置IBOutlet不会保留实例变量;但如果可用的话,再次使用setter。
答案 1 :(得分:11)
从内存管理的角度来看,IBOutlet声明了一些东西(IBOutlet实际上是#defined as nothing)。将IBOutlet包含在声明中的唯一原因是,如果您打算在Interface Builder中连接它(这是IBOutlet声明的用途,提示IB)。
现在,为实例变量创建@property的唯一原因是,如果您打算以编程方式分配它们。如果你不这样做(也就是说,你只是在IB中设置你的用户界面),那么你是否要创建一个属性并不重要。没理由,IMO。
回到你的问题。如果你只是在IB中设置这个ivar(usernameField),不要打扰属性,它不会影响任何东西。如果你为usernameField创建一个属性(因为你以编程方式创建它),肯定会为它创建一个属性,并且如果是这样,绝对要使属性保留。
答案 2 :(得分:5)
实际上有两种模式:
这些模型是Objective-C 2.0之前的模型,并且继承自Mac OS X.它仍然有效,但您不应声明属性来修改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];
}
您必须声明IBOutlet变量的属性:
@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中释放变量:
- (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:来设置出口。内部运行时(特别是_decodeObjectBinary)检查setter方法是否存在。如果它不存在(只有ivar存在),它会向ivar发送额外的保留。因此,如果没有setter方法,则不应保留IBOutlet。
答案 3 :(得分:2)
在您开始使用属性提供的访问器之前,这两个接口定义的工作方式没有任何区别。
在这两种情况下,您仍然需要在dealloc或viewDidUnload方法中释放和设置nOn。
IBOutlet指向在XIB文件中实例化的对象。该对象由XIB文件的File的Owner对象拥有(通常是声明IBOutlet的视图控制器。
因为对象是由于加载XIB而创建的,所以它的保留计数为1并且由文件所有者拥有,如上所述。这意味着文件所有者负责在解除分配时释放它。
使用retain属性添加属性声明只是指定setter方法应保留传入的对象以进行设置 - 这是正确的方法。如果您未在属性声明中指定retain,则IBOutlet可能指向可能不再存在的对象,因为它由其所有者释放,或者在程序生命周期的某个时刻自动释放。保留它可以防止该对象被解除分配,直到您完成它为止。
答案 4 :(得分:1)
nib文件中的对象创建时保留计数为1,然后自动释放。当它重建对象时 层次结构,UIKit使用setValue:forKey:重新建立对象之间的连接,它使用 如果没有可用的setter方法,则默认使用setter方法或保留对象。这意味着您拥有插座的任何对象仍然有效。但是,如果有任何顶级对象没有存储在出口中,则必须保留loadNibNamed:owner:options:方法返回的数组或数组中的对象,以防止这些对象过早释放。
答案 5 :(得分:0)
好吧,在第二种情况下,你要为特定的IBOutlet添加一个getter / setter方法。无论何时添加getter / setter方法,您(几乎总是)都希望将其设置为保留内存管理问题。我认为提出问题的更好方法就是:
@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;
在这种情况下,那么是的,你需要添加一个retain,因为它会影响内存管理。即使它可能没有任何影响,如果您以编程方式添加和删除IBOutlet,您可能会遇到问题。
作为一般规则:每当有IBOutlet时,总是添加@property(带保留)。