《Objective-C編程全解》 讀書筆記 五章 基於引用計數的內存管理

第五章 基於引用計數的內存管理
內存管理的必要性:
內存泄漏:程序未能釋放已經不再使用的內存叫內存泄漏。
懸垂指針(野指針):指針指向已經被釋放或回收的對象。
管理內存的三種方式:引用計數,自動引用計數,自動垃圾回收。

引用計數:
創建對象時,引用計數的初始值爲1。爲防止將要使用的實例對象被釋放,會先向該實例對象發送retain,使其引用計數加1。
擁有實例所有權的對象叫所有者。
不再需要某個對象時,發送release,使引用計數減1.釋放內存的不是release,而是dealloc。和alloc不同,dealloc是實例方法。方法retainCount可以獲得對象引用計數的當前值。
在重寫dealloc的方法中,在釋放自身之前,首先要做好善後工作(釋放所有需要釋放的資源)。這些善後工作包括通過使用release放棄自身的實例變量的所有權。

自動釋放機制:
自動釋放池的典型用法如下:
id pool = [[NSAutoreleasePool alloc] init];
/*
在此進行一系列操作
給臨時對象發送autorelease消息。
*/
[pool release];    //銷燬自動釋放池,自動釋放池中的所有的對象也被銷燬。
在自動釋放池中登錄的實際上相當於放棄了所有權的對象,被稱爲臨時對象。

使用自動釋放池需要注意的地方:
autorelease雖然是NSObject類的方法,但必須和類NSAutoreleasePool一起使用。

臨時對象的生成:
除了使用alloc,init方法創建一個對象這種標準的創建方法外,還有一種創建臨時對象的方法。通過這種方法創建的對象都是臨時對象,生成之後會被加入到內部的自動釋放池。例如:Cocoa裏用於處理字符串的類NSString,由UTF-8編碼的c風格字符串生成NSString對象的方法有兩個:
- (id)initWithUTF8String:(const char *)bytes
alloc生成的實例對象的初始化方法,生成的實例對象的初始引用計數爲1
 +(id)stringWithUTF8String:  (const chat *)    bytes
生成臨時變量的類方法,生成的實例對象會被自動加入到自動釋放池中。
這種類方法的命名規則是不以init開頭,而以要生成的對象的類型(類名)作爲開頭。
同綜合使用alloc init創建對象的方法相比,通過這種方法生成的對象的所有者不是調用stringWithUTF8String類方法的對象。
這種生成臨時對象的類方法,在OC中稱爲便利構造函數或便利構造器。在面向對象的語言中,會把生成對象的函數叫做構造函數,把在內部調用別的構造函數而生成的構造函數叫做便利構造函數。在OC中,便利構造函數指的就是這種利用alloc,init和autorelease生成的臨時對象的類方法。

常量對象:
內存中的常量對象(類對象,常量字符串對象等)的空間分配與其他對象不同,他們沒有引入計數機制永遠不能釋放這些對象。給這些對象發送消息retainCount後,返回的是NSUIntegerMax(其值爲0xffffffff,被定義爲最大的無符號整數)。

單例模式:某個類僅能生成一個實例,程序中訪問到這個類的對象時使用的都是同一個實例對象,在設計模式中這種情況被稱爲單例模式。Cocoa框架中有很多單例模式的應用,這些類通過以shared開頭的類方法返回唯一的實例對象。





ARC概要:

ARC是一個編譯期技術,利用此技術可以簡化OC在內存管理方面的工作量。現在ARC只能管理OC對象,不能管理通過malloc申請的內存。

利用ARC編程要遵守的規則:
禁止調用引用計數相關的方法(retain,release,autorelease,retainCount)。

管理自動釋放池的新語法:
禁止使用NSAutoreleasePool,而是使用新語法@autoreleasepool來管理自動釋放池。格式如下:
@autoreleasepool  {

}

在舊的寫法中,在自動釋放池被初始化至被釋放期間,不可以使用break,return,goto等跳轉語句,否則對象有可能無法被重新釋放。
而在新的語法中,因爲要運行到@autoreleasepool塊外的時候進行對象的釋放,所以可以使用跳轉語句。
另外,@autoreleasepool在非ARC模式下也能用,並且使用@autoreleasepool比使用NSAutoreleasePool性能更好效率更高。

方法族:
同對象生成相關的方法集合叫作方法族。
採用引用計數方式管理內存時,如果不使用alloc/init/new/copy/mutableCopy 這些方法,或者不使用retain來保留一個對象,就不能成爲對象的所有者。另外,只有使用release或autorelease,才能放棄這個對象的所有權。這些規定被叫做所有權策略。
由於ARC允許混合鏈接手動內存管理和自動內存管理的代碼,所以需要定義能夠讓編譯器明確區分的方法。
一個方法要屬於某個方法族除了需要滿足返回值和方法的類別方面的要求外,也需要滿足以下命名規則:
即選擇器的名字由方法族的名字加上非小寫字母開頭的字符串構成,或選擇器通方法族名相同(開頭的_可忽略 eg:_init:locale屬於init方法族)
目前爲止一共定義了五個方法族:alloc方法族,copy方法族,mutableCopy方法族,init方法族,new方法族。這些方法族均表示調用者對被創建的對象擁有所有權。
init開頭的方法必須被定義爲實例方法,其他四個開頭的方法既可以是類方法,也可以是實例方法。切記嚴守內存管理相關的函數命名規則。不嚴格遵守可能會造成無法釋放,或誤釋放。

ARC基本注意事項:
  1. 禁止調用引用計數相關的方法(retain,release,autorelease,retainCount)。
  2. 禁止使用NSAutoreleasePool,而是使用新語法@autoreleasepool來管理自動釋放池
  3. 切記嚴守內存管理相關的函數命名規則
  4. 不用在dealloc中釋放實例變量(但可以釋放資源),也不需要調用[ super  dealloc ];
  5. 編譯代碼時使用clang編譯器,並加上編譯選項-fobjc-arc

循環引用和弱引用:
像這種兩個對象互相引用,或者像A持有B、B持有C、C持有A這樣多個對象的引用關係並
成了環的現象,叫作循環引用或循環保持(retain cyele)。循環引用會造成內存泄漏,只有打破循環
引用關係才能夠釋放內存。

爲了避免循環引用的出現,我們還需要另外一種類型的變量,這種變量能夠引用對象,但不會成爲對象的所有者,不影響對象本身的回收,所以ARC引入了弱引用的概念。弱引用是通過存儲一個指向對象的指針創建的,且不保留對象。OC中用                  
_ _weak修飾符來定義弱引用。

通常聲明的未加_ _weak修飾符的變量都是強引用(strong reference)類型的變量,聲明時也可以通過加上_ _strong修飾符來明示變量是強引用類型。函數和方法的參數也是強引用類型。聲明變量的時候,_ _weak或者_ _strong可以出現在聲明中的任意位置,如下面的例子所示。但有一點要注意的是,最後一個聲明的變量f前不可省略_ _weak

_ _weak Nsobject *a,*b;
NSObject _ _weak *c,*d;
NSObject * _ _weak e, * _ _weak f;

這種用於修飾指針類型變量的修飾符被叫作生命週期修飾符或所有權修飾符。生命週期修飾符一共有四種:
_ _weak
_ _strong
_ _autoreleasing
_ _unsafe_unretained

弱引用會在其指向的實例對象被釋放後自動變成nil,所以不用擔心變成野指針。雖然弱引用好處有很多,但也不能濫用。下面這行代碼是一個極端的例子:
_ _weak People *w = [[People alloc] initWithName:"chenzhen”];
因爲賦值了一個弱引用,所以生成的對象會被立刻釋放。

對象之間引用關係的基本原則:
對象圖中的環路就是循環引用產生的原因,使用ARC的時候應該儘量保證對象之間的關係呈樹形結構避免同一個對象同時被兩處引用。這樣的話,當一個對象被釋放的時候,這個對象的引用對象也會被自然的釋放。

使用ARC編程時其他一些注意事項:
使用ARC的時候,如果既不想保持賦值的對象,也不想賦值的對象在釋放後被自動的設爲nil,可以使用生命週期修飾符:
_ _unsafe_unretained。該修飾符所修飾的變量稱爲非nil化的弱指針,也就是說,如果所指向的內存區域被釋放了,這個指針就是一個野指針了。

還有一點要注意的是,手動內存管理時也有可能在賦值時沒對變量進行retain操作,這就相當於在ARC中用_ _weak、 _ _unsafe_unretained修飾了變量的行爲。因此,將這種代碼遷移到ARC環境的時候一定要小心,需要重新考慮所有權方面的問題。

有一些類的實例不能使用自動化nil的弱引用,但可以使用 _ _unsafe_unretained來代替_ _ weak。

在使用ARC的程序中,id類型和void*類型之間不能進行轉型。就算加了 _ _unsafe_unretained修飾符,轉型操作在編譯時也會報錯。這是因爲,iOS世界中主要有兩種對象:Objective-C對象和Core Foundation對象。其中,Core Foundation類型的對象不在ARC的管理範疇內。因此,當轉換這兩種類型(一種有ARC管理,一種沒有ARC管理)時,就需要告訴編譯器怎樣處理對象的所有權。爲了解決這一問題,可以使用_ _bridge修飾符來實現id類型與void*類型的相互轉換(更多詳細內
容請參考附錄B)。

有一種減少野指針出現的方法是,當不再使用傳入的對象時,將其賦值爲nil

通過函數的參數返回結果對象:
當一個函數或方法有多個返回值時,我們可以通過函數或方法的參數傳入一個指針,將返回值寫入指針所指向的空間。C語言中把這種方法叫作按引用傳遞(pass-by-reference ).Objective-C 的ARC中也有類似的方法,但採用了和C語言不同的實現方式,叫作寫回傳(pass-by-writeback),寫回傳經常被用於當一個方法在處理過程中出現錯誤時,通過指向NSError的二重指針返回錯誤的種類和原因(關於錯誤處理的詳細內容,請參考18.5節)。下面這個聲明是NSString的一個初始化函數,它會從指定的文件讀入內容來完成NSString的初始化。如果初始化失敗,則通過error返回錯誤的種類和原因。error是二重指針類型,*error指向的是NSError*類型的變量。

只可以把nil或臨時變量的指針用於寫回傳,不可以把靜態變量的指針,數組首地址的指針或內部變量的指針用於寫回傳。

將二重指針用於方法的參數時,可以給二重指針加上out修飾符。out修飾符原本時被用於提高調用分佈式對象的效率的,使用寫回傳的情況下,方法調用和方法返回的時候都會發生值傳遞,而通過使用out修飾符,就可以使函數只在返回的時候發生值傳遞,從而省略調用函數時的值傳遞。

c語言數組保存objective-c對象:
ARC有效的程序中可以用c語言數組保存oc對象。

動態分配內存可以使用calloc函數。ARC中不可以使用memset(),bzero(),memcpy()等操作內存的函數,因爲ARC會監視這些函數的行爲,所以使用後就有可能造成內存段錯誤。

ARC有效的情況下,不可以在c語言結構體(或共用體)中定義oc對象,原因是編譯器不能自動釋放結構體內的oc對象。如果非要用的話,可以使用_ _unsafe_unretained來修飾結構體中的oc變量。這樣一來編譯器就不會管理這個內存的變量,完全需要手動管理內存。(引用計數也不可用)。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章