讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 12.7 保持循環與弱引用 >

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(或其他對像)賦給該引用,從而防止其變成野指針。