Cocoa基本原理指南 Cocoa對象 生命周期
Cocoa基本原理指南 Cocoa對象 生命周期是本文要介紹的內容, Cocoa對象的生命周期(至少是潛在地)跨越不同的階段。它需要被創建、初始化、和使用(就是其它對象向它發送消息),它可能被保持、拷貝、或者歸檔,并最終被釋放和銷毀。下面的討論將給出一個典型對象的生命周期框圖,但仍然不涉及太多的細節。
讓我們從最后開始,即從清理對象的方式開始討論。和其它編程語言不同,Objective-C沒有自動釋放不再使用的對象的“垃圾收集”設施。垃圾收集開銷大而且不靈活,取而代之的是,Cocoa和Objective-C選擇一種主動的、策略驅動的例程來保持對象,并在不再需要的時候進行清理。
這種例程和策略建立在引用計數的基礎上。每個Cocoa對象都帶有一個整數,用于指示對其持久性感興趣的其它對象(甚至是例程代碼的現場)的數目。這個整數被稱為對象的保持數(“保持” 是為了避免和“引用”重復)。當您通過alloc或者allocWithZone:類方法創建對象的時候,Cocoa做了一些非常重要的工作:
它將對象的isa指針—NSObject類中唯一的公共實例變量—指向對象的類,因此將對象集成到類層次的運行時視圖中(進一步信息請參見"對象的創建"部分)。
它將對象的保持數—一個隱藏的實例變量,所有對象都有—設置為1(這里假定對象的創建者對其持久性感興趣)。
為對象分配內存之后,您通常需要將其實例變量設置為合理的初始值,以便進行初始化(NSObject聲明了init方法作為這個目的的原型)。這樣對象就可以使用了,您可以向它發送消息,將它傳遞給其它對象,等等。
請注意:由于除了顯式分配的對象之外,初始化方法也可以返回一個對象,因此習慣上將alloc消息嵌套在init消息(或其它初始化方法)中—舉例來說:
- id anObj = [[MyClass alloc] init];
當您釋放一個對象—也就是向對象發送一個release消息時—NSObject會減少它的保持數。如果保持數從1下降到0,對象就會被解除分配。對象的解除分配有兩個步驟:首先是對象的dealloc方法被調用,以釋放實例變量和動態分配的內存;然后是操作系統將對象的本身銷毀,并回收對象占用的內存。
重要提示:您永遠不應該直接調用對象的dealloc方法。
如果您不希望對象很快消失,該怎么辦呢?如果您在創建對象之后向它發送一個retain消息,對象的保持數就會增加到2。這樣,就需要兩個release消息才能導致對象的釋放。圖2-3描述了這種極為簡單的場景。
對象的生命周期—簡化視圖
當然,在這個場景下,對象的創建者不需要保持對象,它已經擁有對象了。但是,如果這個創建者要將對象傳遞給消息中的另一個對象,則情況就不一樣了。在Objective-C程序中,人們假定從其它對象接收到的對象在其被得到的作用域內總是正當的。負責接收的對象可以向被接收的對象發送消息,而且可以將它傳遞給其它對象。這個假設要求對象的發送者“行為規矩”,不要在客戶對象仍然擁有被發送對象的引用時將它過早釋放。
如果客戶對象在程序的作用域之外希望保持接收到的對象,則可以保持該對象—也就是向它發送一個retain消息。保持一個對象會增加該對象的保持數,從而表示希望擁有該對象。客戶對象有責任在一段時間后釋放該對象。如果對象的創建者將該對象釋放,但同時有一個客戶對象已經保持了該對象,則該對象會一直持續到客戶對象將它釋放為止。圖2-4說明了這個序列:
保持接收到的對象
您可以不保持對象,而是通過發送copy或copyWithZone:消息來對其進行拷貝(很多子類—如果不是大多數的話—都封裝了某種數據采納方法,或遵循這種協議)。拷貝一個對象不僅僅是對其進行復制,而且幾乎總是將它的保持數設置為1(請參見圖2-5)。根據對象的本質和可能的用法,拷貝可以是淺拷貝,也可以是深拷貝。深拷貝將對象復制為被拷貝對象的一個實例變量,而淺拷貝只是復制那些實例對象的引用。
在用法方面,copy和retain的區別在于前者要求成為對象新的、唯一的擁有者;新的擁有者可以修改拷貝后的對象,而不考慮其原始對象。一般地說,您需要對值對象(即對某些簡單的值進行封裝的對象)進行拷貝,而不是保持。特別是當對象是可變的時候,比如一個NSMutableString對象。對于不可變對象,copy和retain可能是等價的,其實現方法也是類似的。
拷貝接收到的對象
您可能已經注意到,用這種策略管理對象的生命周期有一個潛在的問題,就是創建一個對象并將它傳遞給另一個對象的對象本身并不總是知道什么時候可以安全地釋放對象。在調用堆棧中可能有多個該對象的引用,某些引用可能來自創建者不知道的對象。如果創建者對象釋放了其所創建的對象,而其它對象向這個已經被銷毀的對象發送消息,程序就會崩潰。為了解決這個問題,Cocoa引入了一種延遲對象釋放的機制,稱為自動釋放(autoreleasing)機制。
自動釋放機制通過自動釋放池(由NSAutoreleasePool類定義)來實現。自動釋放池是位于顯式定義的作用域內的一個對象集合,該作用域被標志為最后釋放。自動釋放池可以嵌套。當您向一個對象發送一個autorelease消息時,Cocoa就會將該對象的一個引用放入到最新的自動釋放池。它仍然是個正當的對象,因此自動釋放池定義的作用域內的其它對象可以向它發送消息。當程序執行到作用域結束的位置時,自動釋放池就會被釋放,池中的所有對象也就被釋放(參見圖2-6)。如果您正在開發應用程序,可能不需要建立一個自動釋放池,Application Kit會自動建立一個自動釋放池,其作用域為為應用程序的事件周期。
自動釋放池
到目前為止,對象生命周期的討論主要關注整個周期中的對象管理機制。但是,指導如何使用這些機制的是對象的所有權策略。這個策略可以概括如下:
如果您通過分配和初始化(比如[[MyClass alloc] init])的方式來創建對象,您就擁有這個對象,需要負責該對象的釋放。這個規則在使用NSObject的便利方法new時也同樣適用。
如果您拷貝一個對象,您也擁有拷貝得到的對象,需要負責該對象的釋放。
如果您保持一個對象,您就部分擁有這個對象,需要在不再使用時釋放該對象。
反過來,
如果您從其它對象那里接收到一個對象,則您不擁有該對象,也不應該釋放它(這個規則有少數的例外,在參考文檔中有顯式的說明)。
和其它規則一樣,這些策略也有一些例外和經常出錯的地方:
如果您通過類工廠方法來創建對象(比如NSMutableArray arrayWithCapacity:方法),則可以假定您接收到的對象已經自動被放到自動釋放池了。您不應該自行將它釋放,如果您需要保持該對象,則應該保持(retain)它。
為了避免循環引用,子對象不能保持它的父對象(父對象是該子對象的創建者,或者將該子對象作為實例變量持有的對象)。
請注意:在上面的原則中提到的“釋放”是指向對象發送一個release或autorelease消息。
如果您沒有遵循這個所有權的策略,則可能導致您的Cocoa程序出現兩種不好的結果:由于沒有釋放自己創建、拷貝、或保持的對象,您的程序會發生內存泄露;或者,由于向已經解除分配的對象發送消息,您的程序發生崩潰。而且還會有進一步的問題:調試這些問題可能相當費時間。
對象的生命周期中可能發生的另一個基本事件是歸檔。歸檔是將組成一個面向對象程序中的相關對象形成的網狀結構—對象圖—轉化為一種可持久的形式(通常是一個文件),該形式可以保存對象圖中對象的標識和彼此之間的關系。在解檔時,可以通過這個檔案重新構造出程序的對象圖。為了參與歸檔(和解檔),對象必須支持通過NSCoder類定義的方法對實例變量進行編碼(和解碼)。為了這個目的,NSObject采納了NSCoding協議。有關對象歸檔的更多信息,請參見"對象的歸檔"部分。
進一步閱讀: 這個Cocoa對象的生命周期概述揭示了這個主題的一些表面的東西。關于Cocoa對象內存管理的更詳細討論,請參見Objective-C編程語言一書中的“Objective-C運行系統”部分,以及Cocoa內存管理編程指南中的內容。
小結:Cocoa基本原理指南 Cocoa對象的生命周期的內容介紹完了,希望本文對你有所幫助!