结论:
虽然现在用weak
还是strong
并没有什么实质性差别,但是除非是有特定需要避免循环引用才用weak
,否则按照苹果官方的观点,目前对于IBOutlet
的最佳实践应该是使用strong
起因:
最近用SwiftFormat工具来格式化swift代码,有个现象引起了我的兴趣:
用这个工具格式化的swift代码了里面的IBOutlet
属性里面的weak
修饰符都消失了,也就是它在格式化的时候把IBOutlet
属性变成了用strong
修饰。
正巧最近新公司发了电脑,新安装了Xcode,发现新安装的Xcode拖IBOutlet
属性也是默认选择strong
。
在之前的开发中我一直以为IBOutlet
默认就应该是weak
,从没考虑过strong
情况,但是这两个肯定不是巧合,其内在应该是有原因的。
研究:
其实这个问题在StackOverFlow里早就有相关话题和讨论结果:
Should IBOutlet be strong or weak under ARC?
stackoverflow.jpg大意就是:
苹果建议除非是有特定需要避免循环引用才使用
weak
,否则目前对于IBOutlet
的最佳实践应该是使用strong
WWDC 2015的”Implementing UI Designs in Interface Builder”会议,则有个Apple的工程师说:
“通常你应该用strong来修饰你的outlet,尤其是当你要连接一个outlet到子视图或者在视图层次中不总是被持有的constraint(约束?)时.只有当你用自定义的视图来引用一个东东,而这个东东反过来又引用了视图层次时才需要用weak,但还是不推荐这么做.”
同时回答者在Twitter上询问了苹果的IB团队的一个工程师确认了这个事情。
那具体到用weak
和strong
到底有什么区别呢?
我们在InterfaceBuilder里面往ViewController里拖一个label时,这个label是加到了这个ViewController的view上,view有一个subViews属性,这个属性是一个数组,里面是这个view的所有子view,而我们加的控件就位于这个数组中,所以实际上我们的控件对象是属于view的,也就是说view对加到它上面的控件是强引用。我们往ViewController里面拖属性时如果使用weak
或者strong
修饰,引用关系分别如下图(实线箭头代表强引用,虚线箭头代表弱引用):
从图上可以看出来,这两种修饰方式其实差别不大,唯一的区别在于如果中间的view变成了nil,label是否会被释放
如果是weak
修饰,view变成了nil时,label只有VC的弱引用,没有被强引用了,就会被系统释放
如果是strong
修饰,view变成了nil时,label还被VC强引用,所以不会被释放
那问题来了,什么情况下一个ViewController的view会被变成nil呢?
这要追溯到早期的iOS版本了,在iOS6之前,ViewController生命周期里有个viewDidUnload方法,开发时间比较久的开发者或者是在一些过时的面试题中会看到这个方法,它的作用是:
In iOS 5 and earlier, when a low-memory condition occurred and the current view controller's views were not needed, the system could opt to call this method after the view controller's view had been released. This method was your chance to perform any final cleanup. If your view controller stored separate references to the view or its subviews, you could use this method to release those references. You could also use this method to remove references to any objects that you created to support the view but that are no longer needed now that the view is gone. You would not use this method to release user data or any other information that cannot be easily recreated.
In iOS 6 and later, clearing references to views and other objects in your view controller is unnecessary.
At the time this method is called, the view property is nil.
简单说就是iOS6之前,当系统内存过低时,系统会自动释放掉已经不需要用到的VC的view,当这个VC的view被Release
之后,就会调用viewDidUnload
方法,如果开发者在VC里面存储了对view或其subviews的单独引用,则需要在这个方法里面释放掉这些引用。
也就是说,苹果认为如果VC的view被release
了,那么它引用的subviews同样应该被release。结合上面的图来看,如果用weak
修饰label,那么label的生命周期就和view一样,view为nil,label也会自动为nil,而用strong
修饰IBOutlet
,label不会自动被释放,开发者需要自己在viewDidUnload
方法里面手动把label置为nil。相比之下,还是前一种方法简洁省事,所以我认为苹果官方当时建议使用weak
来修饰应该是出于这个原因。
这也是有时代背景的,早期iPhone内存都比较小,为了把内存利用到极致,要尽力去优化每个app的内存占用,所以在VC的生命周期里面设计了这么一个viewDidUnload
方法,而到了2011、2012年,内存翻倍了以后,可能苹果通过评估和监测,发现viewDidUnload已经极少被触发,即使low-memory被触发,系统也可以通过其他方式腾挪出更多内存给前台app(我推测的)。所以在iOS6的时候苹果认为这个方法已经没用了,就废弃了这个方法。
而iOS6之后,VC的view再也不会被系统自动release
了,我们作为开发者,什么情况下会去主动把VC的view置为nil呢?个人觉得基本不会有这样的操作,在我几年的开发工作中还没有遇到,也想不到有什么场景下是需要保留VC而单独把view置nil的。所以在此时用weak
修饰IBOutlet
的内在要求已经消失了,在view不可能为nil的情况下,用weak
还是strong
已经没有什么区别,无论是用weak
还是strong
修饰,IBOutlet
属性的生命周期都是一样的。实际上我在之前的开发中一直是使用weak来修饰的,并没有因为违背苹果的推荐做法而出问题。
那按照苹果建议使用strong
修饰有什么好处呢?
……
好像没有。因为目前看来使用weak
还是strong
没有区别。
不过既然必须使用weak
的理由没有了,苹果官方又建议使用strong
(虽然没有在文档里明说,但是从新下载的Xcode默认是strong
,以及IB团队的工程师的说法佐证),那么我们还是尽量使用strong,跟着官方的建议走。如果未来苹果要在这个地方做文章,比如对IBOutlet
做一些优化,那肯定也是按照strong
为前提来做的,我们的代码不会出问题。
网友评论