写这篇文章的缘由,是因为我泡在stackoverflow上翻帖子,看到一个名为Should IBOutlets be strong or weak under ARC? 的帖子很热,而我对被采纳为标准答案的回答也有一些话要补充,我想对于每一个初识ARC模式的人来说,都会有这个疑问,所以不妨我也来和大家探讨一下。
有人问,在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属性
- #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
用右侧按钮,控制相机按钮的隐藏和显示:
- #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; //导致相机按钮显示不出来的原因(释放了leftBarButtonItem),add by cc
- isShowing = NO;
- }else {
- self.controlBtn.title = @"隐藏相机";
- self.navigationItem.leftBarButtonItem = cameraBtn;
- isShowing = YES;
- }
- }
- @end
实验结果是,第一次隐藏了相机按钮后,就再也显示不出来了。原因很简单,cameraBtn指向了空,我们丢失了cameraBtn的对象所有权。
解决问题的办法有两个:
1.不在xib或者storyboard上拖相机按钮,而是用代码创建,自己控制对象所有权
2.将 cameraBtn 定义为strong
我想强调的当然是第二种方法,当然了,改成strong后,相应的也需要配合ARC做下工作:
- - (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)仍然可以使用
一些评论:
1. 使用IBOutlet之后(不设置成weak),对于xib文件中的创建的控件,viewcontroller与view都会强应用该控件,等于该控件的引用计数为2。在内存过小警告释放view时,会导致该控件引用计数只减去了1,该控件没有随view一起被释放(viewcontroller还强引用着它),导致内存泄露,所以需要显式在viewDidUnload中将此控件=nil,释放之。或者直接设置成weak,这样,view被释放时此控件也一起被释放了。
2. 感觉self.navigationItem.leftBarButtonItem = nil; 不是很合适,在arc中就等于直接给释放了,所以就丢失了引用。这里的需求只是不显示而已,用hidden=YES是不是更好?
一点意见,欢迎讨论