本条要点:(作者总结)
- 将某些引用设为 weak,可避免出现 “保留环”。
- weak 引用可以自动清空,也可以不自动清空。自动清空(autonilling)是随着 ARC 而引入的新特性,由运行期系统来实现。在具备自动清空功能的弱引用上,可以随意读取其数据,因为这种引用不会指向已经回收过的对象。
对象图里经常会出现一种情况,就是几个对象都以某种方式互相引用,从而形成“环”(cycle)。由于 Objective-C 内存管理模型使用引用计数架构,所以这种情况通常会泄漏内存,因为最后没有别的东西会引用环中的对象。这样的话,环里的对象就无法为外界所访问了,但对象之间尚有引用,这些引用使得它们都能继续存活下去,而不会为系统所回收。
最简单的保留环由两个对象构成,它们互相引用对方,图举例说明了这种情况。
两个对象通过彼此之间的强
这种保留环的产生原因不难理解,且很容易就能通过查看代码而侦测出来:
1 #import2 3 @class EOCClassA; 4 5 @class EOCClassB; 6 7 8 9 @interface EOCClassA : NSObject10 11 @property (nonatomic, strong) EOCClassB *other;12 13 @end14 15 16 17 @interface EOCClassB : NSObject18 19 @property (nonatomic, Strong) EOCClassA *other;20 21 @end
看代码很容易就能发现其中可能出现的保留环: 如果把 EOCClassA 实例的 other 属性设置成某个 EOCClassB 实例,而把这个 EOCClassB 实例的 other 属性又设置成这个 EOCClassA 实例,那么就会出现图中的保留环。
图中保留环中只剩一个对象还为对象图里的其他对象所引用,移除此引用后整个保留环就泄漏了
保留环会导致内存泄漏。如果只剩一个引用还指向保留环中的实例,而现在又把这个引用移除,那么整个保留环就泄漏了。也就是说,没办法再访问其中的对象了。图中所示的保留环更复杂一些,其中有四个对象,只有 ObjectB 还为外界所引用,把仅有的这个引用移除之后,四者所占内存就泄漏了。
Mac OS X 平台的 Objective-C 程序有个选项,可以启用垃圾收集器(garbage collector),它会检测保留环,若发现外界不再引用其中的对象,则将之回收。但是,从 Mac OS X 10.8 开始,垃圾收集机制就废弃了,而且iOS 系统从未支持过这项功能。因此,从一开始编码时就要注意别出现保留环。
避免保留环的最佳方式就是弱引用。这种引用经常用来表示 “非拥有关系”(nonowning relationship)。将属性声明为 unsafe_unretained 即可。修改刚才那段范例代码,将其属性声明如下:
1 @import2 3 @class EOCClassA; 4 5 @class EOCClassB; 6 7 @interface EOCClassA : NSObject 8 9 @property (nonatomic, strong) EOCClassB *other;10 11 @end12 13 14 15 @interface EOCClassB : NSObject16 17 @property (nonatomic, unsafe_unretained) EOCClassA *other;18 19 @end
修改之后,EOCClassB 实例就不再通过 other 属性来拥有 EOCClassA 实例了。属性特质 (attribute) 中的 unsafe_unretained 一词表明,属性值可能不安全,而且不归此实例所拥有。如果系统已经把属性所指的那个对象回收了,那么在其上调用方法可能会使应用程序崩溃。由于本对象并不保留属性对象,因此其有可能为系统所回收。
用 unsafe_unretained 修饰的属性特质,其语义同 assign 特质等价(参见第 6 条)。然而,assign 通常只用于 “整体类型”(int、float、结构体等),unsafe_unretained 则多用于对象类型。这个词本身就表明其所修饰的属性可能无法安全使用(unsafe)。
Objective-C 中还有一项与 ARC 相伴的运行期特性,可以令开发者安全使用弱引用:这就是weak 属性特质。它与 unsafe_unretained 的作用完全相同。然而,只要系统把属性回收,属性值就会自动设为 nil。在刚才那段代码中,EOCClassB 的 other 属性可修改如下:
1 @property (nonatomic, weak) EOCClassA *other;
图演示了 unsafe_unretained 与 weak 属性的区别。
unsafe_unretained 与 weak 属性,在其所指的对象回收以后表现出来的行为不同
当指向 EOCClassA 实例的引用移除后,unsafe_unretained 属性仍然指向那个已经回收的实例,而 weak 属性则指向 nil。
但是,使用 weak 属性并不是偷懒的借口。在刚才那个例子中,如果在 EOCClassA 对象已经回收之后,引用它的 EOCClassB 实例仍然存活,那么就是编码错误。发生这种情况,就是 bug。开发者应确保程序中不出现此问题。然而,使用 weak 而非 unsafe_unretained 引用可以令代码更安全。应用程序也许会显示出错误的数据,但不会直接崩溃。这么做显然比令终端用户直接看到程序退出要好。不过无论如何,只要在所指对象已经彻底销毁后还继续使用弱引用,那就是依然是个 bug。比方说,用户界面中某个元素会把数据源设置给某个属性,并通过它来查询将要显示的数据。这种属性通常是弱引用。假如还未等界面元素查询完整源对象就已经回收,那么,继续使用弱引用虽不致程序崩溃。但却无法再查到数据了。
一般来说,如果不拥有某对象,那就不要保留它。这条规则对 collection 例外,collection 虽然并不直接拥有其内容,但是它要代表自己所属的那个对象来保留这些元素。有时,对象中的引用会指向另外一个并不归自己拥有的对象,比如 Delegate 模式就是这样(参见第23条)。
END