首页 » iOS编程基础:Swift、Xcode和Cocoa入门指南 » iOS编程基础:Swift、Xcode和Cocoa入门指南全文在线阅读

《iOS编程基础:Swift、Xcode和Cocoa入门指南》12.7 保持循环与弱引用

关灯直达底部

如第5章所述,当两个对象彼此引用时就会陷入保持循环当中,比如,每个对象都是另外一个对象的实例属性值。如果这种情况存在,并且没有其他对象指向这两个对象中的任何一个,那么这两个对象就都不会销毁,因为每个对象的保持计数都大于0,谁都不会“先迈出一步”并释放另外一个。除了彼此,这两个对象不会再由其他对象所指向,我们也没有任何办法补救,最终这两个对象就会导致内存泄漏。

解决办法就是改变对引用的内存管理方式。在默认情况下,引用都是个持久引用(ARC称为strong或retain引用);为其赋值会保持被赋的值。在Swift中,你可以将引用类型的变量声明为weak或unowned,从而改变内存管理的方式:

weak

weak引用会利用到ARC特性的强大功能。如果引用是弱引用,那么ARC就不会保持赋给它的对象。这看起来很危险,因为对象可能会在我们不知情的情况下销毁,留下一个野指针,后面可能会导致潜在的崩溃风险。不过ARC是非常聪明的。它会记录下所有的弱引用以及赋给它们的所有对象。当这样一个对象的保持计数减为0时,那就会销毁该对象,ARC会自动将nil赋给该引用,这也是Swift中weak引用必须是声明为var的Optional的原因所在,这样ARC就可以将ni赋给它了。如果能够前后一致地处理好Optional,那就不会出现任何问题。

unowned

unowned引用则完全不同。在将引用标记为unowned时,你实际上会告诉ARC不要理睬它:为该引用赋值时,ARC不会做任何内存管理工作。这实际上有些危险,如果被引用的对象销毁了,那就会留下一个野指针,应用就可能会崩溃。除非知道被引用的对象不会销毁,否则就不应该使用unowned。如果被引用对象的存活时间比引用它的对象长,那么unowned就是安全的。因此,unowned对象应该是单个对象,只被赋值一次,否则引用者将不复存在。

在实际开发中,弱引用常常用于将一个对象连接到其委托上(参见第11章)。委托是个独立的实体;通常来说对象都不会将自己声明为其委托的所有者,实际上对象常常属于其委托,而不是委托的所有者。所有权常常是颠倒过来的;对象A创建并保持了对象B,并让自己成为对象B的委托。这可能会导致保持循环。因此,大多数委托都应该声明为弱引用:


class ColorPickerController : UIViewController {    weak var delegate: ColorPickerDelegate?    // ...}  

但遗憾的是,持有弱引用的内建Cocoa类的属性有时不是ARC弱引用(因为这些类太老了,还要保持向后兼容,而ARC是比较新的概念)。这种属性会通过关键字assign声明。比如,AVSpeechSynthesizer的delegate属性的声明如下所示:


@property(nonatomic, assign, nullable)    id<AVSpeechSynthesizerDelegate> delegate;  

在Swift中,该声明如下所示:


unowned(unsafe) var delegate: AVSpeechSynthesizerDelegate?  

图12-3:向野指针发送消息造成了崩溃

Swift中的unowned与Objective-C中的assign是一个意思;它们都是告诉你这里不会使用ARC内存管理。Swift还会发出unsafe警告;对于你自己的代码来说,除非安全,否则你是不会使用unowned的,Cocoa的unowned则是存在潜在风险的,你需要格外小心。

即便你的代码使用了ARC,但如果Cocoa代码没有使用,那就表示还可能会出现内存管理问题。诸如AVSpeechSynthesizer的delegate这样的引用最终可能会变成一个野指针(如果该引用所指向的对象销毁了),指向了垃圾。如果你或Cocoa通过该引用发送了消息,那么应用就会崩溃,而这常常出现在真正的错误发生很久之后,所以寻找崩溃根源就会变得相当困难。这种崩溃的典型症状是在与内存管理活动交互时出现EXC_BAD_ACCESS(如图12-3所示)。(这种情况需要打开Zombies进行调试,本章后面将会对此进行介绍。)

防止这种情况出现的责任在于你自己。如果将某个对象赋给了非ARC的不安全引用,如AVSpeechSynthesizer的delegate,并且该对象会在引用尚存的情况下销毁,那么你就需要将nil(或其他对象)赋给该引用,从而防止其变成野指针。