天天看點

iOS記憶體管理iOS記憶體管理1.引用計數2. 引用計數式記憶體管理的思考方式3. ARC

iOS記憶體管理

主要參考資料:《Effective Objective-C 2.0》,《Objective-C進階程式設計 iOS與OS X多線程和記憶體管理》

在學習記憶體管理的時候,查閱了不少資料,零零散散的記錄在有道雲筆記中,在這裡總結提煉一下,希望在友善自己檢視的同時能幫助到大家。

1.引用計數

在引用計數架構下,每個對象都有個可以遞增或遞減的計數器,用以表示目前有多少個事物想令此對象繼續存活下去。這在OC中叫做“保留計數(retain count)”,不過也可以叫做“引用計數”。如果想使某個對象繼續存活,那就遞增其引用計數;用完了之後,就遞減其引用計數。計數變為0,就表示沒人關注此對象了,于是就可以把它銷毀。

MRC:在手動引用計數模式下,NSObject協定聲明了下面三個方法用于操作計數器,retain,release,autorelease。

- (instancetype)retain OBJC_ARC_UNAVAILABLE;

- (oneway void)release OBJC_ARC_UNAVAILABLE;

- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;

ARC:蘋果的官方說明:

在OC中采用ARC機制,讓編譯器來進行記憶體管理。在新一代Apple LLVM編譯器中設定ARC為有效狀态,就無需再次鍵入retain或release代碼,這在降低程式崩潰、記憶體洩露等風險的同時,很大程度上減少了開發程式的工作量。編譯器完全清楚目标對象,并能立即釋放那些不再被使用的對象。如此一來,應用程式将具有可預測性,且能流暢運作,速度也将大幅提升。

雖然現在項目中推薦使用ARC,但是了解MRC是非常有用的。

2. 引用計數式記憶體管理的思考方式

讓我們先忽略ARC,來考慮引用計數式記憶體管理的思考方式。

  1. 自己生成的對象,自己所持有
  2. 非自己生成的對象,自己也能持有
  3. 不再需要自己持有的對象時釋放該對象
  4. 非自己持有的對象無法釋放

2.1 自己生産的對象,自己所持有

使用以下名稱開頭的方法名意味着自己生成的對象自己持有:
  • alloc
  • new
  • copy
  • mutableCopy
在OC中方法名能展現出記憶體管理語義,以上面這4個名稱開頭的方法名表示“生成的對象自己持有”,也可以說成“生成的對象歸調用者所有”。“生成的對象自己持有”意味着:調用上述四種方法的那段代碼要負責釋放方法所傳回的對象。
{
    //調用[[NSObject alloc]init]方法會在堆上産生一個對象,并且沒有被release和autorelease。這個堆上的對象目前的引用計數為1
    //把這個對象的指針指派給obj。指派不會導緻引用計數的變化,相當于通過obj可以拿到這個堆上的對象使用了。
    id obj = [[NSObject alloc]init];

    //這個代碼段快要結束了,把這個堆上的對象釋放掉,不然會産生記憶體洩漏。
    [obj release];  
}
           

2.2 非自己生成的對象,自己也能持有

用alloc/new/copy/mutableCopy以外的方法取得的對象,因為非自己生成并持有,是以自己不是該對象的所有者。

//取得非自己生産并持有的對象
//調用[NSMutableArray array]方法,會在堆上産生一個對象,這個對象被調用了autorelease方法,它會再下一次運作循環的時候被release。可以把這個對象想象成引用計數為0的對象(雖然是過一段時間才變為0),隻有retain一下,才能持有這個對象。
id obj = [NSMutableArray array];
//取得的對象存在,但自己不持有對象
//NSMutableArray對象被賦給變量obj,但變量obj自己并不持有該對象。使用retain方法可以持有對象。
//取得非自己生産并持有的對象
id obj = [NSMutableArray array];
//取得的對象存在,但自己不持有對象
[obj retain];
//自己持有對象
           

通過retain方法,非自己生成的對象跟用alloc/new/copy/muableCopy方法生成并持有的對象一樣,成為了自己所持有的。

2.3 不再需要自己持有的對象時釋放該對象

自己持有的對象一旦不再需要,持有者有義務釋放該對象。釋放使用release方法。

//自己生産并持有對象
id obj = [[NSObject alloc]init];
//自己持有對象
[obj release];
//釋放對象,指向對象的指針仍然被保留在變量obj中,貌似能夠通路,但對象一經釋放絕對不可通路。
           

如此,用alloc方法由自己生成并持有的對象就通過release方法釋放了。

用alloc/new/copy/mutableCopy方法生産并持有的對象,或者用retain方法持有的對象,一旦不再需要,務必要用release方法進行釋放。

如果要用某個方法生成對象,并将其返還給該方法的調用方,那麼它的源代碼又是怎樣的呢?

- (id)allocObject
{
    //自己生成并持有對象
    id obj = [[NSObject alloc]init];
    //自己持有對象
    return obj;
}
           

如上例所示,原封不動地傳回用alloc方法生成并持有的對象,就能讓調用方也持有該對象,請注意allocObject是符合命名規則的。

allocObject名稱符合前文的命名規則,是以它與用alloc方法生成并持有的對象的情況完全相同,是以使用allocObject方法也就意味着“自己生成并持有對象”。

那麼,調用[NSMutableArray array]方法使取得的對象存在,但自己不持有對象,又是如何實作的呢?根據命名規則,不能使用以alloc/new/copy/mutableCopy開頭的方法名,是以使用object這個方法名。

-(id)object
{
    id obj = [[NSObject alloc]init];
    //自己持有對象
    [obj autorelease];
    //取得的對象存在,但自己不持有對象
    return obj;
}
           

上例中我們使用了autorelease方法。使用該方法,可以使取得的對象存在,但自己不持有對象。autorelease提供這樣的功能,使對象在超出指定的生存範圍時能夠自動并正确地釋放(調用release方法)。

autorelease是把對象注冊到自動釋放池,等pool結束時自動調用release方法。

使用NSMutableArray類的array類方法等可以取得誰都不持有的對象,這些方法都是通過autorelease而實作的。此外,根據命名規則,這些用來取得誰都不持有的對象的方法名不能以alloc/new/copy/mutableCopy開頭,這點需要注意。

id obj1 = [obj0 object];
//取得的對象存在,但自己不持有
//當然,也能通過retain方法将調用autorelease方法取得的對象變為自己持有。
           
id obj1 = [obj0 object];
//取得的對象存在,但自己不持有
[obj1 retain];
//通過retain使得自己持有對象
           

2.4 非自己持有的對象無法釋放

對于用alloc/new/copy/mutableCopy方法生成并持有的對象,或是用retain方法持有的對象,由于持有者是自己,是以在不需要該對象時需要将其釋放。而由此以外所得到的對象絕對不能釋放。倘若在應用程式中釋放了非自己所持有的對象就會造成崩潰。

//自己生成并持有對象
id obj = [[NSObject alloc]init];
//自己持有對象
[obj release];
//對象已釋放
[obj release];
//釋放之後再次釋放已非自己持有的對象,應用崩潰
           

或者在“取得的對象存在,但自己不持有對象”時釋放

id obj1 = [obj0 object];
//取得的對象存在,但自己不持有對象
[obj1 release];
//釋放了非自己持有的對象,這肯定會導緻應用程式崩潰
           

幫助了解:

使用與持有是兩回事。你可以使用這個對象,但是你并不是它的所有者。比如有個對象的所有者已經對它進行了autorelease,你可以使用這個對象,但是除非你進行了retain,否則你不是它的所有者。

從所有權的角度考慮記憶體管理問題,就能很容易解釋為什麼需要在description方法中向傳回的NSString對象發送autorelease消息:

因為該隻是建立了一個NSString對象,但是并不想擁有該對象。建立NSString的對象隻是為了傳回一個結果,将NSString對象“交出去”而已。交出去後,别人愛擁有就擁有,不愛擁有就算了。

3. ARC

實際上“引用計數式類存管理”的本質部分在ARC中并沒有改變。就像“自動引用計數”這個名稱表示的那樣,ARC隻是自動地幫助我們處理“引用計數”的相關部分。在編譯機關上,可設定ARC有效或無效,這一點便能佐證上述結論。比如對每個檔案可選擇使用或不使用ARC。

上述的記憶體管理思考方式在ARC中依然有效,隻是在源代碼的的書寫上有些不同。到底有什麼樣的變化呢?首先要了解ARC中追加的所有權聲明。

3.1所有權修飾符

OC程式設計中為了處理對象,可将變量類型定義為id類型或各種對象類型。

所謂對象類型就是指向NSObject這樣的OC類的指針,例如NSObject*。id類型用于隐藏對象類型的類名部分,相當于C語言中常用的“ void* ”。

ARC有效時,id類型和對象類型同C語言其他類型不同,其類型上必須附加所有權修飾符。所有權修飾符一共有4種。

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing

3.1.1 __strong修飾符

__strong修飾符是id類型和對象類型預設的所有權修飾符。

id obj = [[NSObject alloc]init];

該obj變量實際上被附加了__strong所有權。相當于:

id __strong obj = [[NSObject alloc]init]; 
           

再看如下的代碼段:

{
 //ARC
 id __strong obj = [[NSObject alloc]init];
}
           

此源代碼明确指定了C語言的變量的作用域。ARC無效時,該源代碼可記述如下:

//ARC無效
{
 id obj = [[NSObject alloc]init];
 [obj release];
}
           

如此源代碼所示,附有__strong修飾符的變量obj在超出其變量作用域時,即在該變量被廢棄時,會釋放其被賦予的對象。

如strong這個名稱所示,__strong修飾符表示對對象的“強引用”。持有強引用的變量在超出其作用域時被廢棄,随着強引用的失效,引用的對象會随之釋放。

3.1.2 __weak修飾符

有些時候會出現循環引用的問題:

{
        Dog* dog1 = [[Dog alloc]init];//狗對象;dog1持有狗對象的強引用
        Cat* cat1 = [[Cat alloc]init];//貓對象;cat1持有貓對象的強引用
        [dog1 setObj:cat1];//狗對象裡的成員變量obj_持有貓對象的強引用
        [cat1 setObj:dog1];//貓對象裡的成員變量obj_持有狗對象的強引用
        //現在狗對象的持有者是dog1、貓對象的成員變量obj
        //現在貓對象的持有者是cat1、狗對象的成員變量obj
    }
    //因為dog1,cat1超出作用域,強引用失效,是以自動釋放狗對象和貓對象,引用計數從2變為1
    //此時兩個對象中的成員互相引用兩個對象,發生記憶體洩露
           

像下面這種情況,雖然隻有一個對象,但在該對象持有其自身時,也會發生循環引用(自引用)。

id test = [[Test alloc]init];
    [test setObject:test];
           

使用__weak修飾符可以避免循環引用。弱引用不能持有對象,對對象的引用計數沒有影響。還是那句話,可以使用但不持有,持有者把它release了,你就不能使用了。把之前的成員變量的修飾符改成__weak即可解決循環引用問題。

__weak修飾符還有另一個優點。在持有某對象的弱引用時,若該對象被廢棄,則此弱引用将自動失效且被指派為nil。通過檢查附有__weak修飾符的變量是否為nil,可以判斷被指派的對象是否已廢棄。

3.1.3 __unsafe_unretained修飾符

附有_unsafe_unretained修飾符的變量同附有__weak修飾符的變量一樣,因為自己生成并持有的對象不能繼續為自己所有,是以生成的對象會立即被釋放。到這裡,__unsafe_unretained修飾符和__weak修飾符是一樣的。

__unsafe_unretained修飾的變量與__weak有點差別是:在對象被廢棄時,不會被指派為nil。

3.1.4 __autorelease修飾符

用附有__autoreleasing修飾符的變量替代autorelease方法。

前面說到,用alloc/new/copy/mutableCopy以外的方法取得的對象,因為非自己生成并持有,是以自己不是該對象的所有者。

我們自己編寫不以這些開頭的方法時,編譯器會按照規則幫我們在要傳回的對象上調用autorelease方法。

有種情況:

比如NSData中的一個方法:

我們需要聲明一個

NSError *error;

然後把

&error

傳入方法中。

這裡的

(NSError **)errorPtr

,其實等同于

(NSError *  __autoreleasing *)errorPtr
           

這樣就會把*error加入到自動釋放池中。之是以這樣做,是為了符合記憶體管理的思考方式,作為alloc/new/copy/mutableCopy方法傳回值取得的對象是自己生成并持有的,其他情況下便是取得非自己生成的對象。是以,使用附有__autoreleasing修飾符的變量作為對象取得參數,與除alloc/new/copy/mutableCopy外其他方法的傳回值取得的對象完全一樣,都會注冊到autoreleasepool,并取得非自己生成并持有的對象。