IBOutlet、实例变量和属性:最佳实践
今天,我对有关声明 IBOutlet 和实例变量、管理它们、使用正确的访问器以及正确释放它们的最佳实践进行了各种研究。我已经差不多了,但我有一些小问题,我希望有人能够就最佳实践提出建议。我会将它们格式化为代码并注释问题,以便更容易理解。我排除了一些我认为不相关并且可以安全地假设可以工作的明显部分(例如预处理器内容、@end、所需的实现方法等)。
MyViewController.h
@class OtherViewController;
@interface MyViewController : UIViewController {
NSString *_myString;
BOOL _myBOOL;
}
// The first two properties aren't declared in the interface
// above as per best practices when compiling with LLVM 2.0
@property (nonatomic, retain) OtherViewController *otherViewController;
@property (nonatomic, retain) UIButton *myButton;
@property (nonatomic, copy) NSString *myString;
@property (readwrite) BOOL myBOOL;
MyViewController.m
@implementation MyViewController
// Synthesizing IBOutlets on iOS will cause them to be
// retained when they are created by the nib
@synthesize otherViewController;
@synthesize myButton;
// Assign instance variables so as to force compiler
// warnings when not using self.variable
@synthesize myString = _myString;
@synthesize myBOOL = _myBOOL;
- (void)viewDidLoad {
// QUESTIONS:
// 1. Ignoring convenience methods, can you still alloc and init in dot notation
// even when it's being properly synthesized?
self.myString = [[NSString alloc] initWithString:@"myString"];
self.myString = existingNSStringObject;
// 2. Should you always call methods for IBOutlets and instance variables using dot notation?
// Is there any difference seeing as these aren't directly invoking setters/getters?
[self.myButton setText:self.myString];
[myButton setText:self.myString];
[self.otherViewController.view addSubview:mySubview];
[otherViewController.view addSubview:mySubview];
[self.myButton setAlpha:0.1f];
[myButton setAlpha:0.1f];
self.myButton.alpha = 0.1f;
myButton.alpha = 0.1f;
// 3. How fussy are scalar variables in terms of getters and setters,
// given that there is a @synthesize declaration for them?
self.myBOOL = YES;
myBOOL = NO;
if(self.myBOOL) { ... }
if(myBOOL) { ... }
// 4. On instantiation of new view controllers from NIBs, should you use
// dot notation? (I haven't been doing this previously).
otherViewController = [[OtherViewController alloc] initWithNibName:@"OtherView" bundle:nil];
self.otherViewController = [[OtherViewController alloc] ... ]
}
- (void)viewDidUnload {
// 5. Best practice states that you nil-value retained IBOutlets in viewDidUnload
// Should you also nil-value the other instance variables in here?
self.otherViewController = nil;
self.myButton = nil;
self.myString = nil;
}
- (void)dealloc {
[otherViewController release];
[myButton release];
[_myString release];
}
I've done all sorts of research today on best practices with regards to declaring IBOutlets and instance variables, managing them, using the correct accessors and properly releasing them. I'm pretty much there, but I've got some niche questions that I hope somebody will be able to advise the best practice on. I'll format them as code and comment the questions so as to make it easier to understand. I've excluded some obvious parts that I didn't think were relevant and can be safely assumed to work (like pre-processor stuff, @end, required implementation methods etc).
MyViewController.h
@class OtherViewController;
@interface MyViewController : UIViewController {
NSString *_myString;
BOOL _myBOOL;
}
// The first two properties aren't declared in the interface
// above as per best practices when compiling with LLVM 2.0
@property (nonatomic, retain) OtherViewController *otherViewController;
@property (nonatomic, retain) UIButton *myButton;
@property (nonatomic, copy) NSString *myString;
@property (readwrite) BOOL myBOOL;
MyViewController.m
@implementation MyViewController
// Synthesizing IBOutlets on iOS will cause them to be
// retained when they are created by the nib
@synthesize otherViewController;
@synthesize myButton;
// Assign instance variables so as to force compiler
// warnings when not using self.variable
@synthesize myString = _myString;
@synthesize myBOOL = _myBOOL;
- (void)viewDidLoad {
// QUESTIONS:
// 1. Ignoring convenience methods, can you still alloc and init in dot notation
// even when it's being properly synthesized?
self.myString = [[NSString alloc] initWithString:@"myString"];
self.myString = existingNSStringObject;
// 2. Should you always call methods for IBOutlets and instance variables using dot notation?
// Is there any difference seeing as these aren't directly invoking setters/getters?
[self.myButton setText:self.myString];
[myButton setText:self.myString];
[self.otherViewController.view addSubview:mySubview];
[otherViewController.view addSubview:mySubview];
[self.myButton setAlpha:0.1f];
[myButton setAlpha:0.1f];
self.myButton.alpha = 0.1f;
myButton.alpha = 0.1f;
// 3. How fussy are scalar variables in terms of getters and setters,
// given that there is a @synthesize declaration for them?
self.myBOOL = YES;
myBOOL = NO;
if(self.myBOOL) { ... }
if(myBOOL) { ... }
// 4. On instantiation of new view controllers from NIBs, should you use
// dot notation? (I haven't been doing this previously).
otherViewController = [[OtherViewController alloc] initWithNibName:@"OtherView" bundle:nil];
self.otherViewController = [[OtherViewController alloc] ... ]
}
- (void)viewDidUnload {
// 5. Best practice states that you nil-value retained IBOutlets in viewDidUnload
// Should you also nil-value the other instance variables in here?
self.otherViewController = nil;
self.myButton = nil;
self.myString = nil;
}
- (void)dealloc {
[otherViewController release];
[myButton release];
[_myString release];
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我总是声明并显式设置属性的基础实例变量。预先需要做更多的工作,但在我看来,显式区分变量和属性并一目了然地查看类具有哪些实例变量是值得的。我还为实例变量名称添加了前缀,因此如果我不小心输入了
property
而不是object.property
,编译器会发出警告。调用 alloc / init 创建一个保留计数为 1 的对象。您的合成属性也将保留该对象,从而在释放该对象时导致内存泄漏(除非您立即释放您的属性,但这是不好的形式)。最好在单独的行上分配/释放对象。
点表示法实际上与调用
[self setObject:obj]
相同。不使用点表示法直接访问底层实例变量。在init
和dealloc
中,始终直接访问实例变量,因为访问器方法可能包含在对象被调用时无效的额外操作(例如键值观察通知)被创建或被破坏。所有其他时间都使用合成的访问器方法。即使您现在没有做任何特殊的事情,您稍后也可能会重写这些方法以更改设置变量时发生的情况。标量的工作方式相同,只是您不必太担心内存。
一个访问合成的访问器方法,另一个直接访问实例变量。再看一遍问题一和问题二,小心内存泄漏!
视图控制器可能会再次被推送到屏幕上,在这种情况下,您的
viewDidLoad
方法将被第二次调用。如果您要在 viewDidLoad 中设置初始值,请继续将属性设置为 nil 。这对于使用大量内存并且不会影响视图状态的属性来说是有意义的。另一方面,如果您希望该属性持续存在,直到您确定不再需要它为止,请在init
方法中创建它,并且在dealloc
之前不要释放它。I always declare and explicitly set a property's underlying instance variable. It's a little more work up front, but in my mind it's worth it to explicitly differentiate variables and properties and see at a glance what instance variables a class has. I also prefix instance variable names, so the compiler complains if I accidentally type
property
instead ofobject.property
.Calling alloc / init creates an object with a retain count of 1. Your synthesized property will also retain the object, causing a memory leak when it's released (unless you release your property right after, but that's bad form). Better to alloc / and release the object on a separate line.
Dot notation is effectively the same as calling
[self setObject:obj]
. Not using dot notation accesses the underlying instance variable directly. Ininit
anddealloc
, always access the instance variable directly as the accessor methods can include extra operations (such as key value observing notifications) that are not valid when the object is being created or destroyed. All other times use the synthesized accessor methods. Even if you're not doing anything special now, you might later override these methods later to change what happens when the variable is set.Scalars work the same way, only you don't have to worry so much about memory.
One accesses the synthesized accessor methods, the other accesses the instance variable directly. See questions one and two again, and be careful about memory leaks!
The view controller may be pushed onto the screen again, in which case your
viewDidLoad
method will be called a second time. If you're setting initial values in viewDidLoad, go ahead and set your properties to nil here. This makes sense for properties that use a lot of memory and aren't going to affect the state of the view. On the other hand if you want the property to persist until you're sure it's no longer needed, create it in yourinit
method and don't release it untildealloc
.1)你有点误解了@synthesize。 @synthesize 对该对象不执行任何操作。它仅告诉编译器根据 @property 声明中使用的选项生成 getter 和 setter 方法
outlet 不会被保留(outlet只是对界面构建器的通知,不会影响代码),对象在setter生成时被保留使用@synthesize。加载笔尖时,加载系统会调用生成的 setter。
2) 决定是否在 Objective C 中使用访问器与决定在任何其他面向对象语言中使用访问器没有什么不同。这是风格、需求和稳健性的选择。访问器充当 IBOutlet 没有什么区别。
但客观上 CI 建议你不要在两个地方使用访问器:dealloc 和 var 的访问器方法本身。
如果您在 init 中使用访问器,那么您需要小心保留计数。
该行会泄漏内存。使用复制访问器会保留该对象,因此您应该在创建它后在此处释放它。
3)不知道你所说的挑剔是什么意思。可能请参阅 2) 4) 的答案
,请参阅 2) 并注意内存管理。如果您调用 alloc/init,您现在负责释放该对象 - 这完全独立于访问器和 dealloc 使用的保留/释放。
5)不,你不应该在viewDidUnload中将其他实例变量置零。即使视图消失,您的控制器也应该保持其状态。 viewDidUnload 仅用于当控制器的视图当前不在屏幕上时清理可能占用大量内存的视图对象。
考虑一个导航控制器。视图控制器 1 在堆栈上,然后视图控制器 2 被推送并且现在可见。如果内存不足,系统可能会尝试卸载视图控制器 1 的视图,然后调用 viewDidUnload。
然后弹出视图控制器 2 将不会再次创建视图控制器 1 对象,但它会加载视图控制器 1 的视图并调用 viewDidLoad。
回复评论
2)完全正确 - 您可以在分配/初始化和赋值之后立即使用方便的构造函数或释放,或在块退出之前释放,或自动释放。您选择哪个主要是风格问题(尽管有些人会反对自动释放 - 但不是我!)
3)有标量访问器 - 您已经在代码中创建了一些
这将在您的类上创建方法 myBOOL 和 setMyBOOL 。
请记住,点表示法没有什么特别的。这只是为了方便,编译代码时 myObject.property 与 [myObject property] 完全相同,并且 myObject.property = x 与 [myObject setProperty:x] 完全相同。使用点表示法纯粹是一种风格选择。
1) You've slightly misunderstood @synthesize. @synthesize does nothing with the object. It only tells the compiler to generate the getter and setter methods according to the options used in your @property declaration
The outlets aren't retained (outlets are just notices to interface builder and don't affect the code), the objects are retained when the setter generated by @synthesize is used. When the nib is loaded, the loading system calls your generated setter.
2) Deciding whether to use accessors in objective C is no different from deciding to use accessors in any other object oriented language. It is a choice of style, need and robustness. That the accessor is serving as an IBOutlet makes no difference.
But in objective C I would suggest you should NOT use accessors in two places: dealloc and within the var's accessor method itself.
And if you ARE using the accessors in init then you need to be careful about your retain counts.
This line leaks memory. Using your copy accessor retains the object, so you should release it here after creating it.
3) Not sure what you mean by fussy. Possibly see answer to 2)
4) See 2) and be careful about memory management. If you call alloc/init you are now responsible for releasing the object - this is entirely independent of the retains/releases used by accessors and dealloc.
5) No, you should not nil other instance variables in viewDidUnload. Your controller is expected to maintain its state even if the view goes away. viewDidUnload is only for cleaning up potentially memory-heavy view objects when the controller's view is not currently on screen.
Consider a navigation controller. View controller 1 is on the stack and then view controller 2 is pushed and is now visible. If memory conditions get low, the system could attempt to unload view controller 1's view and will then call viewDidUnload.
Then popping view controller 2 will not create the view controller 1 object again, but it WILL load view controller 1's view and call viewDidLoad.
Re comments
2) That's exactly right - you can use a convenience constructor or release immediately after your alloc/init and assignment, or release before the block exits, or autorelease. Which you choose is mostly a matter of style (though some would argue against autorelease - but not me!)
3) There are accessors for scalars - you have created some in your code
This creates methods myBOOL and setMyBOOL on your class.
Remember that there is nothing special about dot notation. It is only a convenience and when the code is compiled myObject.property is exactly equivalent to [myObject property] and myObject.property = x is exactly equivalent to [myObject setProperty:x]. Using dot notation is purely a style choice.
self.myVariable
您可以访问实例变量myVariable
属性的 getter,通过myVariable
您可以访问局部变量。它们不是同一件事。就像:
然后在 viewDidUnload 和 dealloc 方法中调用此方法。
希望有帮助!
self.myVariable
you are accessing the getter of the property of the instance variablemyVariable
and bymyVariable
you are accessing the local variable. They're not the same thing.Like:
and then call this method both in
viewDidUnload
and indealloc
methods.Hope it helps !