iOS記憶體管理

來源:互聯網
上載者:User

標籤:

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中的一個方法:

- (BOOL)writeToFile:(NSString *)path options:(NSDataWritingOptions)writeOptionsMask error:(NSError **)errorPtr;

我們需要聲明一個NSError *error;然後把&error傳入方法中。
這裡的(NSError **)errorPtr,其實等同於

(NSError *  __autoreleasing *)errorPtr

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

iOS記憶體管理

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.