Objective-C 記憶體管理——你需要知道的一切

skyline75489發表於2016-04-15

前言

有關 iOS 記憶體管理的文章相信大家都看過不少了,我自己也一樣。不過網上大部分文章都沒有解決對於記憶體管理的一些關鍵性的疑惑,對於初學者來說並不是很友好。本文旨在從初學者的角度出發,對 iOS 記憶體管理的關鍵部分進行介紹,希望能對廣大 iOS 學習者有所幫助。

本文首發於我發起的 筆試面試知識整理 專案,這裡是對應的 Github 倉庫,由多人協作編輯而成。如發現有錯誤的地方,隨時歡迎指正。

Objective-C 中的記憶體分配

在 Objective-C 中,物件通常是使用 alloc 方法在堆上建立的。 [NSObject alloc] 方法會在對堆上分配一塊記憶體,按照NSObject的內部結構填充這塊兒記憶體區域。

一旦物件建立完成,就不可能再移動它了。因為很可能有很多指標都指向這個物件,這些指標並沒有被追蹤。因此沒有辦法在移動物件的位置之後更新全部的這些指標。

MRC 與 ARC

Objective-C中提供了兩種記憶體管理機制:MRC(MannulReference Counting)和 ARC(Automatic Reference Counting),分別提供對記憶體的手動和自動管理,來滿足不同的需求。現在蘋果推薦使用 ARC 來進行記憶體管理。

MRC

物件操作的四個類別

物件操作 OC中對應的方法 對應的 retainCount 變化
生成並持有物件 alloc/new/copy/mutableCopy等 +1
持有物件 retain +1
釋放物件 release -1
廢棄物件 dealloc

注意:

  • 這些物件操作的方法其實並不包括在OC中,而是包含在Cocoa框架下的Foundation框架中。

  • 物件的 reatinCount 屬性並沒有實際上的參考價值,參考蘋果官方文件《Practical Memory Management》.

四個法則

  • 自己生成的物件,自己持有。

  • 非自己生成的物件,自己也能持有。

  • 不在需要自己持有的物件的時候,釋放。

  • 非自己持有的物件無法釋放。

如下是四個黃金法則對應的程式碼示例:

/*
 * 自己生成並持有該物件
 */
 id obj0 = [[NSObeject alloc] init];
 id obj1 = [NSObeject new];
/*
 * 持有非自己生成的物件
 */
id obj = [NSArray array]; // 非自己生成的物件,且該物件存在,但自己不持有
[obj retain]; // 自己持有物件
/*
 * 不在需要自己持有的物件的時候,釋放
 */
id obj = [[NSObeject alloc] init]; // 此時持有物件
[obj release]; // 釋放物件
/*
 * 指向物件的指標仍就被保留在obj這個變數中
 * 但物件已經釋放,不可訪問
 */
/*
 * 非自己持有的物件無法釋放
 */
id obj = [NSArray array]; // 非自己生成的物件,且該物件存在,但自己不持有
[obj release]; // 此時將執行時crash 或編譯器報error

其中 非自己生成的物件,且該物件存在,但自己不持有 這個特性是使用autorelease來實現的,示例程式碼如下:

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

autorelease 使得物件在超出生命週期後能正確的被釋放(通過呼叫release方法)。在呼叫 release 後,物件會被立即釋放,而呼叫 autorelease 後,物件不會被立即釋放,而是註冊到 autoreleasepool 中,經過一段時間後 pool結束,此時呼叫release方法,物件被釋放。

在MRC的記憶體管理模式下,與對變數的管理相關的方法有:retain, release 和 autorelease。retain 和 release 方法操作的是引用記數,當引用記數為零時,便自動釋放記憶體。並且可以用 NSAutoreleasePool 物件,對加入自動釋放池(autorelease 呼叫)的變數進行管理,當 drain 時回收記憶體。

ARC

ARC 是蘋果引入的一種自動記憶體管理機制,會根據引用計數自動監視物件的生存週期,實現方式是在編譯時期自動在已有程式碼中插入合適的記憶體管理程式碼以及在 Runtime 做一些優化。

變數識別符號

在ARC中與記憶體管理有關的變數識別符號,有下面幾種:

  • __strong

  • __weak

  • __unsafe_unretained

  • __autoreleasing

__strong 是預設使用的識別符號。只有還有一個強指標指向某個物件,這個物件就會一直存活。

__weak 宣告這個引用不會保持被引用物件的存活,如果物件沒有強引用了,弱引用會被置為 nil

__unsafe_unretained 宣告這個引用不會保持被引用物件的存活,如果物件沒有強引用了,它不會被置為 nil。如果它引用的物件被回收掉了,該指標就變成了野指標。

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

變數識別符號的用法如下:

Number* __strong num = [[Number alloc] init];

注意 __strong 的位置應該放到 * 和變數名中間,放到其他的位置嚴格意義上說是不正確的,只不過編譯器不會報錯。

屬性識別符號

類中的屬性也可以加上標誌符:

@property (assign/retain/strong/weak/unsafe_unretained/copy) Number* num

assign 表明 setter 僅僅是一個簡單的賦值操作,通常用於基本的數值型別,例如CGFloatNSInteger

strong 表明屬性定義一個擁有者關係。當給屬性設定一個新值的時候,首先這個值進行 retain ,舊值進行 release ,然後進行賦值操作。

weak 表明屬性定義了一個非擁有者關係。當給屬性設定一個新值的時候,這個值不會進行 retain,舊值也不會進行 release, 而是進行類似 assign 的操作。不過當屬性指向的物件被銷燬時,該屬性會被置為nil。

unsafe_unretained 的語義和 assign 類似,不過是用於物件型別的,表示一個非擁有(unretained)的,同時也不會在物件被銷燬時置為nil的(unsafe)關係。

copy 類似於 strong,不過在賦值時進行 copy 操作而不是 retain 操作。通常在需要保留某個不可變物件(NSString最常見),並且防止它被意外改變時使用。

錯誤使用屬性識別符號的後果

如果我們給一個原始型別設定 strongweakcopy ,編譯器會直接報錯:

Property with `retain (or strong)` attribute must be of object type

設定為 unsafe_unretained 倒是可以通過編譯,只是用起來跟 assign 也沒有什麼區別。

反過來,我們給一個 NSObject 屬性設定為 assign,編譯器會報警:

Assigning retained object to unsafe property; object will be released after assignment

正如警告所說的,物件在賦值之後被立即釋放,對應的屬性也就成了野指標,執行時跑到屬性有關操作會直接崩潰掉。和設定成 unsafe_unretained 是一樣的效果(設定成 weak 不會崩潰)。

unsafe_unretained 的用處

unsafe_unretained 差不多是實際使用最少的一個識別符號了,在使用中它的用處主要有下面幾點:

  1. 相容性考慮。iOS4 以及之前還沒有引入 weak,這種情況想表達弱引用的語義只能使用 unsafe_unretained。這種情況現在已經很少見了。

  2. 效能考慮。使用 weak 對效能有一些影響,因此對效能要求高的地方可以考慮使用 unsafe_unretained 替換 weak。一個例子是 YYModel 的實現,為了追求更高的效能,其中大量使用 unsafe_unretained 作為變數識別符號。

引用迴圈

當兩個物件互相持有對方的強引用,並且這兩個物件的引用計數都不是0的時候,便造成了引用迴圈。

要想破除引用迴圈,可以從以下幾點入手:

  • 注意變數作用域,使用 autorelease 讓編譯器來處理引用

  • 使用弱引用(weak)

  • 當例項變數完成工作後,將其置為nil

Autorelease Pool

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

所謂的延遲傳送release訊息指的是,當我們把一個物件標記為autorelease時:

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

這個物件的 retainCount 會+1,但是並不會發生 release。當這段語句所處的 autoreleasepool 進行 drain 操作時,所有標記了 autorelease 的物件的 retainCount 會被 -1。即 release 訊息的傳送被延遲到 pool 釋放的時候了。

在 ARC 環境下,蘋果引入了 @autoreleasepool 語法,不再需要手動呼叫 autoreleasedrain 等方法。

Autorelease Pool 的用處

在 ARC 下,我們並不需要手動呼叫 autorelease 有關的方法,甚至可以完全不知道 autorelease 的存在,就可以正確管理好記憶體。因為 Cocoa Touch 的 Runloop 中,每個 runloop circle 中系統都自動加入了 Autorelease Pool 的建立和釋放。

當我們需要建立和銷燬大量的物件時,使用手動建立的 autoreleasepool 可以有效的避免記憶體峰值的出現。因為如果不手動建立的話,外層系統建立的 pool 會在整個 runloop circle 結束之後才進行 drain,手動建立的話,會在 block 結束之後就進行 drain 操作。詳情請參考蘋果官方文件。一個普遍被使用的例子如下:

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

如果不使用 autoreleasepool ,需要在迴圈結束之後釋放 100000000 個字串,如果
使用的話,則會在每次迴圈結束的時候都進行 release 操作。

Autorelease Pool 進行 Drain 的時機

如上面所說,系統在 runloop 中建立的 autoreleaspool 會在 runloop 一個 event 結束時進行釋放操作。我們手動建立的 autoreleasepool 會在 block 執行完成之後進行 drain 操作。需要注意的是:

  • 當 block 以異常(exception)結束時,pool 不會被 drain

  • Pool 的 drain 操作會把所有標記為 autorelease 的物件的引用計數減一,但是並不意味著這個物件一定會被釋放掉,我們可以在 autorelease pool 中手動 retain 物件,以延長它的生命週期(在 MRC 中)。

main.m 中 Autorelease Pool 的解釋

大家都知道在 iOS 程式的 main.m 檔案中有類似這樣的語句:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

在面試中問到有關 autorelease pool 有關的知識也多半會問一下,這裡的 pool 有什麼作用,能不能去掉之類。在這裡我們分析一下。

根據蘋果官方文件, UIApplicationMain 函式是整個 app 的入口,用來建立 application 物件(單例)和 application delegate。儘管這個函式有返回值,但是實際上卻永遠不會返回,當按下 Home 鍵時,app 只是被切換到了後臺狀態。

同時參考蘋果關於 Lifecycle 的官方文件,UIApplication 自己會建立一個 main run loop,我們大致可以得到下面的結論:

  1. main.m 中的 UIApplicationMain 永遠不會返回,只有在系統 kill 掉整個 app 時,系統會把應用佔用的記憶體全部釋放出來。

  2. 因為(1), UIApplicationMain 永遠不會返回,這裡的 autorelease pool 也就永遠不會進入到釋放那個階段

  3. 在 (2) 的基礎上,假設有些變數真的進入了 main.m 裡面這個 pool(沒有被更內層的 pool 捕獲),那麼這些變數實際上就是被洩露的。這個 autorelease pool 等於是把這種洩露情況給隱藏起來了。

  4. UIApplication 自己會建立 main run loop,在 Cocoa 的 runloop 中實際上也是自動包含 autorelease pool 的,因此 main.m 當中的 pool 可以認為是沒有必要的。

在基於 AppKit 框架的 Mac OS 開發中, main.m 當中就是不存在 autorelease pool 的,也進一步驗證了我們得到的結論。不過因為我們看不到更底層的程式碼,加上蘋果的文件中不建議修改 main.m ,所以我們也沒有理由就直接把它刪掉(親測,刪掉之後不影響 App 執行,用 Instruments 也看不到洩露)。

Autorelease Pool 與函式返回值

如果一個函式的返回值是指向一個物件的指標,那麼這個物件肯定不能在函式返回之前進行 release,這樣呼叫者在呼叫這個函式時得到的就是野指標了,在函式返回之後也不能立刻就 release,因為我們不知道呼叫者是不是 retain 了這個物件,如果我們直接 release 了,可能導致後面在使用這個物件時它已經成為 nil 了。

為了解決這個糾結的問題, Objective-C 中對物件指標的返回值進行了區分,一種叫做 retained return value,另一種叫做 unretained return value。前者表示呼叫者擁有這個返回值,後者表示呼叫者不擁有這個返回值,按照“誰擁有誰釋放”的原則,對於前者呼叫者是要負責釋放的,對於後者就不需要了。

按照蘋果的命名 convention,以 alloc, copy, init, mutableCopynew 這些方法打頭的方法,返回的都是 retained return value,例如 [[NSString alloc] initWithFormat:],而其他的則是 unretained return value,例如 [NSString stringWithFormat:]。我們在編寫程式碼時也應該遵守這個 convention。

我們分別在 MRC 和 ARC 情況下,分析一下兩種返回值型別的區別。

MRC

在 MRC 中我們需要關注這兩種函式返回型別的區別,否則可能會導致記憶體洩露。

對於 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 執行 drain,把 retain count 減為 1

  4. 當整個物件執行 dealloc 時, release 把 retain count 減為 0,物件被釋放

可以看到沒有記憶體洩露發生。

如果我們只是使用:

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

這一條語句會導致 retain count 增加到 2,而我們少執行了一次 release,就會導致 retain count 不能被減為 0 。

另外,我們也可以使用臨時變數:

NSObject * a = [[NSObject alloc] init];
self.property = a;
[a release];

這種情況,因為對 a 執行了一次 release,所有不會出現上面那種 retain count 不能減為 0 的情況。

注意:現在大家基本都是 ARC 寫的比較多,會忽略這一點,但是根據上面的內容,我們看到在 MRC 中直接對 self.proprety 賦值和先賦給臨時變數,再賦值給 self.property,確實是有區別的!我在面試中就被問到這一點了。

我們在編寫自己的程式碼時,也應該遵守上面的原則,同樣是使用 autorelease:

// 注意函式名的區別
+ (MyCustomClass *) myCustomClass
{
    return [[[MyCustomClass alloc] init] autorelease]; // 需要 autorelease
}
- (MyCustomClass *) initWithName:(NSString *) name
{
    return [[MyCustomClass alloc] init]; // 不需要 autorelease
}

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

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

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

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

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

以及在自己寫的函式中:

+ (MyCustomClass *) myCustomClass
{
    return [[MyCustomClass alloc] init]; // 不用 autorelease
}

這些寫法都是 OK 的,也不會出現記憶體問題。

為了進一步理解 ARC 是如何做到這一點的,我們可以參考 Clang 的文件

對於 retained return value, Clang 是這樣做的:

When returning from such a function or method, ARC retains the value at the point of evaluation of the return statement, before leaving all local scopes.

When receiving a return result from such a function or method, ARC releases the value at the end of the full-expression it is contained within, subject to the usual optimizations for local values.

可以看到基本上 ARC 就是幫我們在程式碼塊結束的時候進行了 release:

NSObject * a = [[NSObject alloc] init];
self.property = a;
//[a release]; 我們不需要寫這一句,因為 ARC 會幫我們把這一句加上

對於 unretained return value:

When returning from such a function or method, ARC retains the value at the point of evaluation of the return statement, then leaves all local scopes, and then balances out the retain while ensuring that the value lives across the call boundary. In the worst case, this may involve an autorelease, but callers must not assume that the value is actually in the autorelease pool.

ARC performs no extra mandatory work on the caller side, although it may elect to do something to shorten the lifetime of the returned value.

這個和我們之前在 MRC 中做的不是完全一樣。ARC 會把物件的生命週期延長,確保呼叫者能拿到並且使用這個返回值,但是並不一定會使用 autorelease,文件寫的是在 worst case 的情況下才可能會使用,因此呼叫者不能假設返回值真的就在 autorelease pool 中。從效能的角度,這種做法也是可以理解的。如果我們能夠知道一個物件的生命週期最長應該有多長,也就沒有必要使用 autorelease 了,直接使用 release 就可以。如果很多物件都使用 autorelease 的話,也會導致整個 pool 在 drain 的時候效能下降。

weak 與 autorelease

眾所周知,weak 不會持有物件,當給一個 weak 賦以一個自己生成的物件(即上面提到的 retained return value)後,物件會立馬被釋放。

一個很常見的 warning 就是 Assigning retained object to weak variable, object will be released after assignment.

但是我們前面也提到了,可以持有非自己生成的物件,這通過 autorelease 實現。

那麼如果一個 weak 被賦以一個非自己生成的物件(即上面提到的 unretained return value)呢?程式碼如下:

NSNumber __weak *number = [NSNumber numberWithInt:100];
NSLog(@"number = %@", number);

這種情況下是可以正確列印值的。

clang的文件 是這麼說的:這種情況下,weak 並不會立即釋放,而是會通過 objc_loadWeak 這個方法註冊到 AutoreleasePool 中,以延長生命週期。

ARC 下是否還有必要在 dealloc 中把屬性置為 nil?

為了解決這個問題,首先讓我們理清楚屬性是個什麼存在。屬性(property) 實際上就是一種語法糖,每個屬性背後都有例項變數(Ivar)做支援,編譯器會幫我們自動生成有關的 setter 和 getter,對於下面的 property:

@interface Counter : NSObject
@property (nonatomic, retain) NSNumber *count;
@end;

生成的 getter 和 setter 類似下面這樣:

- (NSNumber *)count {
    return _count;
}
- (void)setCount:(NSNumber *)newCount {
    [newCount retain];
    [_count release];
    // Make the new assignment.
    _count = newCount;
}

Property 這部分對於 MRC 和 ARC 都是適用的。

有了這部分基礎,我們再來理解一下把屬性置為 nil 這個步驟。首先要明確一點,在 MRC 下,我們並不是真的把屬性置為 nil,而是把 Ivar 置為 nil。

[_property release];
_property = nil;

如果用 self.property 的話還會呼叫 setter,裡面可能存在某些不應該在 dealloc 時執行的程式碼。

對於 ARC 來說,系統會自動在 dealloc 的時候把所有的 Ivar 都執行 release,因此我們也就沒有必要在 dealloc 中寫有關 release 的程式碼了。

在 ARC 下把變數置為 nil 有什麼效果?什麼情況下需要把變數置為 nil?

在上面有關 property 的內容基礎上,我們知道用:

self.property = nil

實際上就是手動執行了一次 release。而對於臨時變數來說:

NSObject *object = [[NSObject alloc] init];
object = nil;

置為 nil 這一句其實沒什麼用(除了讓 object 在下面的程式碼裡不能再使用之外),因為上面我們討論過 ,ARC 下的臨時變數是受到 Autorelease Pool 的管理的,會自動釋放。

因為 ARC 下我們不能再使用 release 函式,把變數置為 nil 就成為了一種釋放變數的方法。真正需要我們把變數置為 nil 的,通常就是在使用 block 時,用於破除迴圈引用:

MyViewController * __block myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler =  ^(NSInteger result) {
    [myController dismissViewControllerAnimated:YES completion:nil];
    myController = nil;
};

YTKNetwork 這個專案中,也可以看到類似的程式碼:

- (void)clearCompletionBlock {
    // nil out to break the retain cycle.
    self.successCompletionBlock = nil;
    self.failureCompletionBlock = nil;
}
ARC 在執行時期的優化

上面提到對於 unretained return value, ARC “並不一定會使用 autorelease”,下面具體解釋一下。

ARC 所做的事情並不僅僅侷限於在編譯期找到合適的位置幫你插入合適的 release 等等這樣的記憶體管理方法,其在執行時期也做了一些優化,如下是兩個優化的例子:

  1. 合併對稱的引用計數操作。比如將 +1/-1/+1/-1 直接置為 0.

  2. 巧妙地跳過某些情況下 autorelease 機制的呼叫。

其中第二個優化,是 ARC 針對 autorelease 返回值提供的一套優化策略,大體的流程如下:

當方法全部基於 ARC 實現時,在方法 return 的時候,ARC 會呼叫 objc_autoreleaseReturnValue() 以替代 MRC 下的 autorelease。在 MRC 下需要 retain 的位置,ARC 會呼叫 objc_retainAutoreleasedReturnValue()。因此下面的 ARC 程式碼:

+ (instancetype)createSark {
    return [self new];
}
// caller
Sark *sark = [Sark createSark];

實際上會被改寫成類似這樣:

+ (instancetype)createSark {
    id tmp = [self new];
    return objc_autoreleaseReturnValue(tmp); // 代替我們呼叫autorelease
}
// caller
id tmp = objc_retainAutoreleasedReturnValue([Sark createSark]) // 代替我們呼叫retain
Sark *sark = tmp;
objc_storeStrong(&sark, nil); // 相當於代替我們呼叫了release

有了這個基礎,ARC 可以使用一些優化技術。在呼叫 objc_autoreleaseReturnValue() 時,會在棧上查詢 return address 以確定 return value 是否會被直接傳給 objc_retainAutoreleasedReturnValue()。 如果沒傳,說明返回值不能直接從提供方傳送給接收方,這時就會呼叫 autorelease。反之,如果返回值能順利的從提供方傳送給接收方,那麼就會直接跳過 autorelease 過程,並且修改 return address 以跳過 objc_retainAutoreleasedReturnValue()過程,這樣就跳過了整個 autoreleaseretain的過程。

核心思想:當返回值被返回之後,緊接著就需要被 retain 的時候,沒有必要進行 autorelease + retain,直接什麼都不要做就好了。

另外,當函式的呼叫方是非 ARC 環境時,ARC 還會進行更多的判斷,在這裡不再詳述,詳見 《黑幕背後的 Autorelease》

關於如何寫一個檢測迴圈引用的工具

Instrument 為我們提供了 Allocations/Leaks 這樣好用的工具用來檢測 memory leak 的工具。如下是記憶體洩露的兩種型別:

  • Leaked memory: Memory unreferenced by your application that cannot be used again or freed (also detectable by using the Leaks instrument).

  • Abandoned memory: Memory still referenced by your application that has no useful purpose.

其中 Leaks 工具主要用來檢測 Leaked memory,在 MRC 時代 程式設計師會經常忘記寫 release 方法導致記憶體洩露,在 ARC 時代這種已經不太常見。(ARC時代 主要的Leaked Memory 來自於底層 C 語言以及 一些由 C 寫成的底層庫,往往會因為忘記手工 free 而導致 leak )。

Allocations 工具主要用來檢測 Abandoned memory. 主要思路是在一個時間切片內檢測物件的宣告週期以觀察記憶體是否會無限增長。通過 hook 掉 alloc,dealloc,retain,release 等方法,來記錄物件的生命週期。

參考資料

  • Objective-C記憶體管理MRC與ARC

  • 10個Objective-C基礎面試題,iOS面試必備

  • 黑幕背後的 Autorelease

  • Objective-C Autorelease Pool 的實現原理

  • How does objc_retainAutoreleasedReturnValue work?

  • https://stackoverflow.com/questions/9784762/strong-weak-retain-unsafe-unretained-assign

  • https://stackoverflow.com/questions/29350634/ios-autoreleasepool-in-main-and-arc-alloc-release

  • https://stackoverflow.com/questions/6588211/why-do-the-ios-main-m-templates-include-a-return-statement-and-an-autorelease-po

  • https://stackoverflow.com/questions/2702548/if-the-uiapplicationmain-never-returns-then-when-does-the-autorelease-pool-get

  • https://stackoverflow.com/questions/6055274/use-autorelease-when-setting-a-retain-property-using-dot-syntax

  • https://stackoverflow.com/questions/17601274/arc-and-autorelease

  • https://stackoverflow.com/questions/8292060/arc-equivalent-of-autorelease

  • https://stackoverflow.com/questions/7906804/do-i-set-properties-to-nil-in-dealloc-when-using-arc

  • http://wereadteam.github.io/2016/02/22/MLeaksFinder/?from=singlemessage&isappinstalled=0

  • http://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-runtime-objc-loadweak

相關文章