http://www.csdn.net/article/2011-06-07/299347
【書摘】iPad應用開發實戰:記憶體管理髮表於2011-06-07 15:49| 661次閱讀| 來源CSDN| 0 條評論|
作者李晨記憶體管理應用開發書摘iPad
摘要:導讀:本文節選自《iPad應用開發實戰》的其中一章,主要對iOS的記憶體管理進行了細緻的講解。本書由資深iOS開發專家(現就任於美國Apple公司)撰寫。 1 對象所有權、引用計數與釋放 當一個所
| 導讀:本文節選自《iPad應用開發實戰》的其中一章,主要對iOS的記憶體管理進行了細緻的講解。本書由資深iOS開發專家李晨(現就任於美國Apple公司)撰寫。 |
1 對象所有權、引用計數與釋放
當一個所有者(owner,其本身可以是任何一個Objective-C對象)做了以下某個動作時,它就擁有了對一個對象的所有權(ownership):
1)建立一個對象。包括使用任何名稱中包含“alloc”、“new”或者“copy”的方法。
2)保留(retain)一個對象。
一個對象可以有多個所有者,一個所有者也可以擁有多個對象。
相應地,引用計數(reference count)增減的基本規則是:
1)當所有者建立一個對象時,該對象的引用計數為1。
2)當所有者保留它時,該對象的引用計數加1。
3)當所有者釋放(release)它時,該對象的引用計數減1。
與此相關,當一個所有者對於一個對象的引用計數的增減總計為0時,它就放棄了對這個對象的所有權。 現在可以從兩個不同的角度來看Objective-C的記憶體管理問題。從對象所有權的角度來看,當一個對象有著至少一個所有者的時候,它依然存在;當它沒有任何所有者的時候,它會被釋放掉。從引用計數的角度看,一個對象存在時,其引用計數大於零;當一個對象的引用計數為零時,它會調用dealloc方法並被釋放掉。這兩個角度的關係是:在所有權的背後起作用的機制是引用計數機制,通過引用計數的增減來理解所有權的概念。只能使用所有權的概念來管理記憶體,如果試圖直接擷取對象的引用計數,那麼得到的數將會令人意外,因為系統的一些架構會“偷偷”增減對象的引用計數。
回顧一下,記憶體管理的目標是:當一個對象的某個所有者依然需要使用它時,要保證這個對象的存在;當一個對象的所有所有者都不再需要它時,要保證這個對象被銷毀。因此只要任何一個所有者在使用完一個對象之後釋放掉它,那麼以上記憶體管理的目標就可以實現。由此可以得出任何一個所有者(記住,所有者本身也只是一個對象)所應當遵守的基本步驟:
擁有一個對象→使用一個對象→放棄對象的所有權(釋放對象)
從引用計數的角度來看就是:
還需要這個對象時,保持對其增減為正;不再需要這個對象時,不增減其引用計數值。
如所示,所有者1和所有者2單獨地執行了擁有對象、使用對象、放棄對象所有權的步驟;當所有者1不再需要該對象時及時放棄了所有權,但此時所有者2依然擁有該對象,因此該對象依然存在,所有者2可以繼續使用它;當所有者2也不再需要該對象時,也放棄所有權,這時對象不再有任何所有者(相應的引用計數變為0),因此立刻被銷毀掉。
兩個所有者對同一個對象的記憶體管理
需要注意的是,所有者2隻是複製了該對象的指標,並沒有使用copy方法,因此複製指標這個操作本身並不增加對象的引用計數。而正因為所有者2希望能使用該對象,因此通過retain方法成為它的所有者,也保證了在所有者1放棄該對象時,該對象不被銷毀。
2 自動釋放與便捷方法
有時候,一個所有者建立一個對象後,會立刻將該對象的指標傳遞給其他所有者。這時,這個建立者不希望再擁有這個對象,但如果立刻給它發送一個release(釋放)訊息,則這個對象被立刻釋放掉,這樣會導致其他所有者還沒有來得及保留該對象。解決這個兩難問題的方法是,給對象發送一個autorelease(自動釋放)訊息,這樣建立者不再擁有該對象的所有權;該對象成為自動釋放的對象,但是不會立刻被釋放掉,其他所有者可以有時間保留或複製該對象,並成為其唯一所有者。
來看一個自動釋放的例子。一個所有者先用alloc方法建立一個對象,此時該所有者擁有這個對象,對象的引用計數為1。緊接著,所有者自動釋放該對象,即放棄了所有權,但對象的引用計數在一段時間內依然為1。我們可以看出自動釋放的另一個好處,就是不會因為在後面忘記給對象發送release訊息而造成記憶體泄露。相關代碼如下所示:
-(Object*)returnAutoreleaseObject { Object* obj = [[Object alloc] init]; return [obj autorelease]; }
與自動釋放相關的有一大類構造方法,由它們構造的對象直接就是自動釋放的對象,這一類構造方法叫做便捷方法。比如下面這個字串就是一個自動釋放的對象,stringWithFormat:就是一個便捷方法。
NSString* string = [NSString stringWithFormat:@"autoreleaseString"];
再舉幾個便捷方法的例子,方便讀者以後的開發。
q NSArray的arrayWithObjects:和arrayWithArray。
q UIImage的imageNamed。
q NSNumber的numberWithBool。
如上所述,autorelease方法會在一段時間以後釋放掉一個對象,在這段時間內可以安全地使用該對象。那麼這段時間究竟是多久呢?我們需要先更多地瞭解自動釋放的機制,再來回答這個問題。
先來看看自動釋放池。自動釋放池是NSAutoreleasePool的執行個體,其中包含了收到autorelease訊息的對象。當一個自動釋放池自身被銷毀(dealloc)時,它會給池中每一個對象發送一個release訊息(如果給一個對象多次發送autorelease訊息,那麼當自動釋放池銷毀時,這個對象也會收到同樣數目的release訊息)。可以看出,一個自動釋放的對象,它至少能夠存活到自動釋放池銷毀的時候。
那麼自動釋放池何時被建立,又何時被銷毀呢?在每一個事件周期的開始,系統會自動建立一個自動釋放池,在每一個事件周期的結尾,系統會自動銷毀這個自動釋放池。一般情況下,可以理解為:當使用者的代碼在持續運行時,自動釋放池是不會被銷毀的,這段時間內使用者可以安全地使用自動釋放的對象。當使用者的代碼運行告一段落,開始等待使用者輸入(或者其他事件)時,自動釋放池就會被釋放掉,池中的對象都會收到一個release訊息,有的可能會因此被銷毀。
到此為止,已經對自動釋放的機制有了一個大體的瞭解。自動釋放而非直接釋放,可以節省一些代碼量,提高開發速度。但是它有一個明顯的缺點:它延緩了對象的釋放,在有大量自動釋放的對象時,會佔用大量記憶體資源。因此,需要避免將大量對象自動釋放。並且,在以下兩種情況下,需要手動建立並手動銷毀掉自動釋放池。
q 當在主線程外開啟其他線程時,系統只會在主線程中自動產生並銷毀自動釋放池。
q 在短時間內製造了大量自動釋放對象時,及時銷毀有助於有效利用iPad上有限的記憶體資源。
3 訪問器方法與屬性
當需要訪問一個對象的成員變數時,不是直接存取這個成員變數,而是藉助於訪問器方法(accessor method),這樣做的好處是協助實現了代碼的封裝。可是,在記憶體管理相對複雜的環境中,要為一個變數實現訪問器方法是一件非常煩瑣的事情,如果手動寫代碼來實現所有的訪問器方法,將會大大降低效率,並容易發生失誤。如果使用一些通用的訪問器方法在訪問成員變數時協助管理記憶體,那麼將大大提高開發的效率並可防止記憶體流失的發生。
在Objective-C中,可以很方便地為對象作出屬性聲明(property declaration),然後讓編譯器去合成(synthesize)對象的訪問器方法。比如屬性聲明如下:
@property int value;
就等價於如下兩個訪問器方法的聲明:
-(int)value;
-(void)setValue:(int)newValue;
而以下指令將告訴編譯器去合成這兩個訪問器方法:
@synthesize value;
注意,編譯器是按照@property後面跟隨的屬性來確定如何合成訪問器方法的,因此為變數聲明屬性至關重要。下面介紹幾組屬性。
q 可寫性(Writability)
readwrite:指定這個屬性會讓編譯器合成getter和setter;如果不指定readonly,那麼readwrite將為預設屬性。
readonly:指定這個屬性會讓編譯器只合成getter,而不合成setter。在這種情況下,如果使用點文法(dot syntax)來給變數賦值,編譯器會報錯,提醒這個變數具有唯讀屬性。
在定義讀寫屬性時應當注意:對於外界唯讀取而不修改的變數,盡量使用readonly屬性;外界毫不關心的變數,則不要為其設定任何屬性。這樣封裝能增加代碼的獨立性和安全性。
q 存入器屬性(Setter Semantics)
assign:表示在setter中只使用簡單的賦值,而不會發送retain訊息給對象。這是預設屬性。當變數類型為scalar時(比如int、CGRect),應當指定這個屬性。
retain:表示在setter中將向舊值發送release訊息,向新值發送retain訊息。
copy:表示在setter中將向舊值發送release訊息,向新值發送copy訊息。
4 改變引用計數的特殊情況
在iOS開發中,當使用外部介面(IBOutlet)時,應當使用如下屬性聲明:
@property (nonatomic,retain) IBOutlet IBObject* object;
以上模式只適用於iOS開發。針對MacOS開發,應當採用assign屬性。
在nib檔案(也就是xib檔案)中的對象被建立時的引用計數為1,然後它們會被自動釋放。隨後,UIKit重建對象之間的結構,會用到可用的setter(如果沒有setter的話,會自行retain對象),這樣外部介面對象的引用計數依然為1,所以即使沒有寫代碼來初始化它們或者保留它們,仍然需要在dealloc方法中釋放它們(事實上,在前兩章的例子中已經這樣做了)。
在3.1和3.2節中提到,當一個對象被建立時引用計數為1,被retain時引用計數加1,被release時引用計數減1,被autorelease後引用計數減1。但這些不是會引起引用計數發生變化的全部情形。比如,Nib對象的使用會“悄悄”改變引用計數。事實上在其他一些特殊情況下,引用計數也會被改變。
q NSArray:當把一個對象添加到NSArray時,對象的引用計數會加1;反之當把對象從NSArray移除時,對象的引用計數會減1。同樣的規則也適用於其子類NSMutableArray。
q UIView:當一個視圖對象使用addGestureRecognizer:添加手勢辨識器時,手勢辨識器的引用計數會加1;反之使用removeGestureRecognizer:時,手勢辨識器的引用計數會減1。類似地,當視圖對象使用addSubview:和removeFromSuperView時,對象的引用計數也會發生變化。
總結一下,容器(container)使用名字中帶有“add”的方法常常會增加對象的引用計數,使用名字中帶有“remove”的方法常常會減少引用計數。容器指的是數組(NSArray)、字典(NSDictionary)、集合(NSSet)等,它們用於集中管理大量對象。
5 記憶體管理總結
下面來總結一下記憶體管理規則:
1)一個對象的引用計數為正時,它依然存在;當它的引用計數為0時,就被銷毀。
2)一個對象通過alloc、create、copy和retain等方法成為另一個對象的所有者。
3)當一個所有者使用完一個對象之後,應當放棄對這個對象的所有權。放棄所有權的方法是發送release給這個對象,使得所有者對這個對象的引用計數的增減為0。
4)一個對象可能有多個所有者。這意味著,當一個所有者放棄所有權後,對象可能依然存在。只有當全部所有者放棄所有權時,對象才被銷毀。
5)給對象發送autorelease訊息,意味著這個對象的引用計數將在未來減1。
6)使用快捷方法建立對象後,不用給對象發送release訊息。
7)使用屬性聲明,並且讓編譯器來合成訪問器方法。
8)為外部介面作出nonatomic和retain的屬性聲明,並記得release它們。
9)一些容器(如NSArray和UIView的執行個體)會通過add方法增加對象的引用計數,通過remove方法減少對象的引用計數。
10)當自己開闢線程,或者建立大量臨時的自動釋放對象時,需要手動建立自動釋放池並銷毀它。
掌握了這些規則,再通過之後的實戰加深對iOS記憶體管理的理解,就能夠開發出高效率的iPad應用了!