研究项目注意到项目中对delegate的修饰都用的是unsafe_unretained.
确实几年前,甚至更早之前的MRC时代用unsafe_unretained以至于后面热门的assign修饰delegate并没有任何争议,甚至还得到推崇。如果爱阅读Apple官方源码的话,就会知道在2016的时候,Apple官方也一直用assign修饰大部分的delegate(注意我为什么说是大部分而不是全部,后面我会解释)
而且当年还在Stack Overflow上引发了关于官方为啥用assign 而不用retain的讨论:
https://stackoverflow.com/questions/918698/why-are-objective-c-delegates-usually-given-the-property-assign-instead-of-retai
原因是它能很好地解决循环引用的问题:
之前常见的delegate往往都是assign方式的属性而不是retain方式的属性,因为赋值不会增加引用计数,就是为了防止delegate两端产生不必要的循环引用。如果一个UITableViewController对象a通过retain获取了UITableView对象b的所有权,已经保持了强引用,而这个UITableView对象b的delegate又是a,如果这个delegate也是retain方式的,那基本上就没有机会释放这两个对象了,就会造成循环引用,进而导致内存泄漏。
而现在再阅读源码,就会发现Apple早已经悄悄的把assign换成了weak。
原因不是因为assign和unsafe_unretained不能解决循环引用的问题,而是他们解决的并不够彻底!
因为声明为assign和unsafe_unretained的指针,虽然不会对对象进行retain,但当对象销毁时,会依然指向之前的内存空间,造成野指针,也就是绝大多数程序有时会出现莫名其妙的小概率崩溃事件原因之一
另一方面当weak修饰的话,它不会对对象进行retain,当对象销毁时,会自动指向nil,完美的避免了野指针问题,相比前者就更干净,彻底。
那么问题就又来了
1.unsafe_unretained真的就不好了么,那为啥很多大开源项目里面还用了它?
因为在MRC时代,当需要修饰对象类型时,MRC时代就会使用unsafe_unretained,而且weak等属性在iOS5才开放,所以为了兼容性的问题,依旧保留了它。
2.既然weak修饰delegate这么优秀,那是不是所有地方都用它修饰delegate就好了?
这也牵扯了为什么我之前说的“Apple也一直用assign修饰大部分的delegate”而并没有说“Apple也一直用assign修饰全部的delegate”的解释
重点来了!
重点来了!
重点来了!
如果你仔细研究Apple源码,你可能就会惊奇地发现一条能推翻我之前全部言论的代码!
在Foundation中的NSURLSession类中,居然官方也用retain修饰delegate:
之前说的所有操作基本都刻意的防止循环引用,为啥Apple官方还专门在这个类中用retain来修饰delegate?
查阅它的官方注释:
The delegate assigned when this object was created.
This delegate object is responsible for handling authentication challenges, for making caching decisions, and for handling other session-related events. The session object keeps a strong reference to this delegate until your app exits or explicitly invalidates the session. If you do not invalidate the session, your app leaks memory until it exits.
Note
This delegate object must be set at object creation time and may not be changed.
SDKs iOS 7.0+, macOS 10.9+, tvOS 9.0+, watchOS 2.0+
Declared In Foundation
More Property Reference
里面值得重点关注的地方,大概翻译是“会话对象保持对该委托的强引用,直到应用程序退出或显式地使会话无效。如果你没有使会话失效,你的应用程序会泄露内存直到它退出。”
细细品味,就能深悟“意料之外,情理之中”这句话
个人理解是因为NSURLSession类是网络接口类,而用retain修饰delegate是为了保证后台网络请求的稳定性,如下载,保证后台下载不会轻易被系统释放掉!
当然不需要后台下载,也要按照apple官方注释中强调的一定要去取消,要去注意释放session,不然会造成leaks memory
除此之外,在Apple的官方源码中还有一些这样强引用的delegate,如:
查阅它的官方注释:
Specifies the receiver’s delegate object.
Defaults to nil.
Important
The delegate object is retained by the receiver. This is a rare exception to the memory management rules described in Advanced Memory Management Programming Guide.
An instance of CAAnimation should not be set as a delegate of itself. Doing so (outside of a garbage-collected environment) will cause retain cycles.
SDKs iOS 2.0+, macOS 10.5+, tvOS 9.0+
Declared In Core Animation
More Property Reference
为啥这里也用strong修饰delegate?
Apple的解释也很有趣
“The delegate object is retained by the receiver. This is a rare exception to the memory management rules described in Advanced Memory Management Programming Guide.”
意思是“它就是个内存管理规则比较罕见的特例!”
个人理解是因为CAAnimation类是动画类,就动画来说都不是单独存在的,它必须要依赖于能展示动画的母体,所以他的生命周期是依赖于layer->view的生命周期的,需要特定的设置展示与释放,比较罕见,所以用强引用保证可靠性。
综上所诉,编程是一个很神奇的东西,可能在现在看某些操作很合乎情理,但估计过段时间它就会被质疑。当然在另一方面,只要对代码理解透彻,可能一些骚操作也会带来很大的收益,这就是编程的魅力吧。