Objective-C 內存管理

Objective-C 內存管理

在 Objective-C 中,對象通常是使用 alloc 方法在堆上創(chuàng)建的。 [NSObject alloc] 方法會在對堆上分配一塊內存,按照NSObject的內部結構填充這塊兒內存區(qū)域。

一旦對象創(chuàng)建完成,就不可能再移動它了。因為很可能有很多指針都指向這個對象,這些指針并沒有被追蹤。因此沒有辦法在移動對象的位置之后更新全部的這些指針。

MRC 與 ARC


Objective-C中提供了兩種內存管理機制:MRC(MannulReferenceCounting)和 ARC(Automatic Reference Counting),分別提供對內存的手動和自動管理,來滿足不同的需求?,F(xiàn)在蘋果推薦使用 ARC 來進行內存管理。

MRC

  • autorelease 使得對象在超出生命周期后能正確的被釋放(通過調用release方法)。在調用 release 后,對象會被立即釋放,而調用 autorelease 后,對象不會被立即釋放,而是注冊到 autoreleasepool 中,經過一段時間后 pool結束,此時調用release方法,對象被釋放。

  • 在MRC的內存管理模式下,與對變量的管理相關的方法有:retain, release 和 autorelease。retain 和 release 方法操作的是引用記數(shù),當引用記數(shù)為零時,便自動釋放內存。并且可以用 NSAutoreleasePool 對象,對加入自動釋放池(autorelease 調用)的變量進行管理,當 drain 時回收內存。

ARC

自動內存管理機制,會根據(jù)引用計數(shù)自動監(jiān)視對象的生存周期,實現(xiàn)方式是在編譯時期自動在已有代碼中插入合適的內存管理代碼以及在 Runtime 做一些優(yōu)化。

  • __strong 是默認使用的標識符。只有還有一個強指針指向某個對象,這個對象就會一直存活。

  • __weak 聲明這個引用不會保持被引用對象的存活,如果對象沒有強引用了,弱引用會被置為 nil

  • __unsafe_unretained 聲明這個引用不會保持被引用對象的存活,如果對象沒有強引用了,它不會被置為 nil。如果它引用的對象被回收掉了,該指針就變成了野指針。

  • __autoreleasing 用于標示使用引用傳值的參數(shù)(id *),在函數(shù)返回時會被自動釋放掉。

引用循環(huán)

當兩個對象互相持有對方的強引用,并且這兩個對象的引用計數(shù)都不是0的時候,便造成了引用循環(huán)。

要想破除引用循環(huán),可以從以下幾點入手:

  • 注意變量作用域,使用 autorelease 讓編譯器來處理引用
  • 使用弱引用(weak)
  • 當實例變量完成工作后,將其置為nil

Autorelease Pool

Autorelase Pool 提供了一種可以允許你向一個對象延遲發(fā)送release消息的機制。當你想放棄一個對象的所有權,同時又不希望這個對象立即被釋放掉(例如在一個方法中返回一個對象時),Autorelease Pool 的作用就顯現(xiàn)出來了。

所謂的延遲發(fā)送release消息指的是,當我們把一個對象標記為autorelease時:

NSString* str = [[[NSString alloc] initWithString:@"hello"] autorelease];

這個對象的 retainCount 會+1,但是并不會發(fā)生 release。當這段語句所處的 autoreleasepool 進行 drain 操作時,所有標記了 autorelease 的對象的 retainCount 會被 -1。即 release 消息的發(fā)送被延遲到 pool 釋放的時候了。

在 ARC 環(huán)境下,蘋果引入了 @autoreleasepool 語法,不再需要手動調用 autorelease 和 drain 等方法。

Autorelease Pool 的用處

在 ARC 下,我們并不需要手動調用 autorelease 有關的方法,甚至可以完全不知道 autorelease 的存在,就可以正確管理好內存。因為 Cocoa Touch 的 Runloop 中,每個 runloop circle 中系統(tǒng)都自動加入了 Autorelease Pool 的創(chuàng)建和釋放。

當我們需要創(chuàng)建和銷毀大量的對象時,使用手動創(chuàng)建的 autoreleasepool 可以有效的避免內存峰值的出現(xiàn)。因為如果不手動創(chuàng)建的話,外層系統(tǒng)創(chuàng)建的 pool 會在整個 runloop circle 結束之后才進行 drain,手動創(chuàng)建的話,會在 block 結束之后就進行 drain 操作。

例子

for (int i = 0; i < 100000000; i++)
{
    @autoreleasepool
    {
        NSString* string = @"ab c";
        NSArray* array = [string componentsSeparatedByString:string];
    }
}

Autorelease Pool 進行 Drain 的時機

如上面所說,系統(tǒng)在 runloop 中創(chuàng)建的 autoreleaspool 會在 runloop 一個 event 結束時進行釋放操作。我們手動創(chuàng)建的 autoreleasepool 會在 block 執(zhí)行完成之后進行 drain 操作。需要注意的是:

  1. 當 block 以異常(exception)結束時,pool 不會被 drain
  2. Pool 的 drain 操作會把所有標記為 autorelease 的對象的引用計數(shù)減一,但是并不意味著這個對象一定會被釋放掉,我們可以在 autorelease pool 中手動 retain 對象,以延長它的生命周期(在 MRC 中)。
  3. Pool drain的時機(Runloop一次周期的休眠前)

Autorelease Pool 與函數(shù)返回值

如果一個函數(shù)的返回值是指向一個對象的指針,那么這個對象肯定不能在函數(shù)返回之前進行 release,這樣調用者在調用這個函數(shù)時得到的就是野指針了,在函數(shù)返回之后也不能立刻就 release,因為我們不知道調用者是
不是 retain 了這個對象,如果我們直接 release 了,可能導致后面在使用這個對象時它已經成為 nil 了。

為了解決這個糾結的問題, Objective-C 中對對象指針的返回值進行了區(qū)分,一種叫做 retained return value,另一種叫做 unretained return value。前者表示調用者擁有這個返回值,后者表示調用者不擁有這個返回值,按照“誰擁有誰釋放”的原則,對于前者調用者是要負責釋放的,對于后者就不需要了。

  • MRC
    在 MRC 中我們需要關注這兩種函數(shù)返回類型的區(qū)別,否則可能會導致內存泄露。

對于 retained return value,需要負責釋放

假設我們有一個 property 定義如下:

@property (nonatomic, retain) NSObject *property;

在對其賦值的時候,我們應該使用:

self.property = [[[NSObject alloc] init] autorelease];

然后在 dealloc 方法中加入:

[_property release];
_property = nil;

這樣內存的情況大體是這樣的:

  1. init 把 retain count 增加到 1
  2. 賦值給 self.property ,把 retain count 增加到 2
  3. 當 runloop circle 結束時,autorelease pool 執(zhí)行 drain,把 retain count 減為 1
  4. 當整個對象執(zhí)行 dealloc 時, release 把 retain count 減為 0,對象被釋放

可以看到沒有內存泄露發(fā)生。


對于 unretained return value,不需要負責釋放

當我們調用非 alloc,init 系的方法來初始化對象時(通常是工廠方法),我們不需要負責變量的釋放,可以當成普通的臨時變量來使用:

NSString *name = [NSString stringWithFormat:@"%@ %@", firstName, lastName];
self.name = name
// 不需要執(zhí)行 [name release]
  • ARC

在 ARC 中我們完全不需要考慮這兩種返回值類型的區(qū)別,ARC 會自動加入必要的代碼,因此我們可以放心大膽地寫:

self.property = [[NSObject alloc] init];
self.name = [NSString stringWithFormat:@"%@ %@", firstName, lastName];

參考:Objective-C 內存管理

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容