在ARC下,IBOutlets到底应该定义成strong 还是 weak ?支持这个答案的人最多,答案仅是摘自官方文档的一个片段:
From a practical perspective, in iOS and OS X outlets should be defined as declared properties. Outlets should generally be weak, except for those from File’s Owner to top-level objects in a nib file (or, in iOS, a storyboard scene) which should be strong. Outlets that you create will therefore typically be weak by default, because:
-
Outlets that you create to, for example, subviews of a view controller’s view or a window controller’s window, are arbitrary references between objects that do not imply ownership.
-
The strong outlets are frequently specified by framework classes (for example, UIViewController’s view outlet, or NSWindowController’s window outlet).
@property (weak) IBOutlet MyView *viewContainerSubview; @property (strong) IBOutlet MyOtherClass *topLevelObject;
大意是说,在 ARC 中,一般outlet属性都推荐使用 weak,应该使用 strong 的 outlet 是 File's Owner连接到 nib 的顶层对象。
什么是 File's Owner连接到 nib 的顶层对象呢?说白话一点,就是自定义的view,不是直接作为main view里面一个sub view直接显示出来,而是需要通过实例化创建出来的。你自己实例化,当然需要strong了,不然谁还替你保留对象所有权呢?
以上的分析都没有错,但是总觉得少了点什么。对于到底是weak 还是 strong,归根结底,还是要刨到对对象所有权的问题上,但是不便于总结出浅显易懂的规律性使用法则。于是,就会有一个又一个的特例打破文档所总结的常规,不明白规则的根是什么,还是会碰到麻烦的。
我来举一个简单的例子,创建一个程序入口指向navigation controller的工程,导航栏上拖2个按钮:
右侧按钮用于控制相机按钮的显示与否,按照文档的指示,我们在程序中定义这两个按钮应为weak属性
- @interface TestViewController : UIViewController
- {
- BOOL isShowing;
- }
- @property (nonatomic,weak)IBOutlet UIBarButtonItem *controlBtn;
- @property (nonatomic,weak)IBOutlet UIBarButtonItem *cameraBtn;
- -(IBAction)controlAction:(id)sender;
- @end
#import <UIKit/UIKit.h>
@interface TestViewController : UIViewController
{
BOOL isShowing;
}
@property (nonatomic,weak)IBOutlet UIBarButtonItem *controlBtn;
@property (nonatomic,weak)IBOutlet UIBarButtonItem *cameraBtn;
-(IBAction)controlAction:(id)sender;
@end
用右侧按钮,控制相机按钮的隐藏和显示:
- @interface TestViewController ()
- @end
- @implementation TestViewController
- @synthesize cameraBtn,controlBtn;
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- // Do any additional setup after loading the view, typically from a nib.
- isShowing = YES;
- }
- - (void)viewDidUnload
- {
- [super viewDidUnload];
- // Release any retained subviews of the main view.
- }
- - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
- {
- return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
- }
- -(IBAction)controlAction:(id)sender
- {
- if (isShowing) {
- self.controlBtn.title = @"显示相机";
- self.navigationItem.leftBarButtonItem = nil;
- isShowing = NO;
- }else {
- self.controlBtn.title = @"隐藏相机";
- self.navigationItem.leftBarButtonItem = cameraBtn;
- isShowing = YES;
- }
- }
- @end
#import "TestViewController.h"
@interface TestViewController ()
@end
@implementation TestViewController
@synthesize cameraBtn,controlBtn;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
isShowing = YES;
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
-(IBAction)controlAction:(id)sender
{
if (isShowing) {
self.controlBtn.title = @"显示相机";
self.navigationItem.leftBarButtonItem = nil;
isShowing = NO;
}else {
self.controlBtn.title = @"隐藏相机";
self.navigationItem.leftBarButtonItem = cameraBtn;
isShowing = YES;
}
}
@end
实验结果是,第一次隐藏了相机按钮后,就再也显示不出来了。原因很简单,cameraBtn指向了空,我们丢失了cameraBtn的对象所有权。
解决问题的办法有两个:
1.不在xib或者storyboard上拖相机按钮,而是用代码创建,自己控制对象所有权
2.将 cameraBtn 定义为strong
我想强调的当然是第二种方法,当然了,改成strong后,相应的也需要配合ARC做下工作:
- {
- [super viewDidUnload];
- // Release any retained subviews of the main view.
- self.cameraBtn = nil;
- }
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
self.cameraBtn = nil;
}
顺便提一下ARC其他属性的规则:
-
strong:等同于"retain",属性成为对象的拥有者
-
weak:属性是 weak pointer,当对象释放时会自动设置为 nil
-
unsafe_unretained:等同于之前的"assign",只有 iOS 4 才应该使用
-
copy:和之前的 copy 一样,复制一个对象并创建 strong 关联
-
assign:对象不能使用 assign,但原始类型(BOOL、int、float)仍然可以使用