一、ARC
ARC 的想法來源于蘋果在早期設(shè)計 Xcode 的 Analyzer 的時候,發(fā)現(xiàn)編譯器在編譯時可以幫助大家發(fā)現(xiàn)很多內(nèi)存管理中的問題。后來蘋果修改了一些內(nèi)存管理代碼的書寫方式,干脆編譯器在編譯時把內(nèi)存管理的代碼都自動補上。
ARC 是編譯器特性,而不是運行時特性,更不是垃圾回收器(GC)。
Automatic Reference Counting (ARC) is a compiler-level feature that simplifies the process of managing object lifetimes (memory management) in Cocoa applications.
程序在編譯的時候,編譯器會分析源碼中每個對象的生命周期,然后基于這些對象的生命周期,編譯器幫我們在合適的地方插入retain、release 等代碼以管理對象的引用計數(shù),從而達到自動管理對象生命周期的目的。
所以 ARC 是工作在編譯期的一種技術(shù)方案,這樣的好處:
編譯之后,ARC 與 MRC 代碼是沒有什么差別的,所以二者可以
在源碼中共存。相對于垃圾回收這類內(nèi)存管理方案,
ARC 不會帶來運行時的額外開銷,所以對于應(yīng)用的運行效率不會有影響。相反的,由于ARC 能夠深度分析每一個對象的生命周期,它能夠做到比人工管理引用計數(shù)更加高效。例如在一個函數(shù)中,對一個對象剛開始有一個引用計數(shù) +1 的操作,之后又緊接著有一個 -1 的操作,那么編譯器就可以把這兩個操作都優(yōu)化掉。
只有編譯器是無法單獨完成這一工作的,還需要 OC 運行時庫的配合協(xié)助,因此 ARC 的實現(xiàn)工具主要包括:
- LLVM 編譯器(clang 3.0 以上)
- OC 運行時庫 493.9 以上
weak 變量能夠在引用計數(shù)為 0 時被自動設(shè)置成 nil,顯然是有運行時邏輯在工作的。
ARC 能夠解決 iOS 開發(fā)中 90% 的內(nèi)存管理問題,但是另外 10% 的內(nèi)存管理問題是需要開發(fā)者處理的,這主要是與底層 Core Foundation 對象交互的部分,底層 Core Foundation 對象由于不在 ARC 的管理下,所以需要自己維護這些對象的引用計數(shù)。
二、ARC 的開啟和關(guān)閉
在 Targets -》Build Settings 中搜索 Automatic Reference Counting,可以修改它的布爾值,yes - 開啟 no - 關(guān)閉。

如果需要對特定文件開啟或關(guān)閉 ARC,可以在 Targets -》Build Phases -》Compile Sources,在里面找到對應(yīng)文件,添加flag:
開啟:-fobjc-arc 關(guān)閉:-fno-objc-arc

三、ARC 的修飾符
主要提供了 4 種修飾符,他們分別是:__strong、__weak、__autoreleasing、__unsafe_unretained。
3.1 __strong
強引用。相當于 @property 的 "strong"。所有對象只有當沒有任何一個強引用指向(引用計數(shù)為 0)時,才會被釋放。
注意:如果在聲明引用時不加修飾符,那么將默認是強引用。當需要釋放強引用指向的對象時,需要將強引用置 nil。
使用 __strong 修飾變量的程序運行過程。
{
id __strong object = [[NSObject alloc] init];
}
轉(zhuǎn)換后的模擬源代碼為:
/*編譯器的模擬代碼*/
id object = objc_msgSend(NSObjct, @selector(alloc));
objc_msgSend(object, @selector(init));
objc_release(object);
對象變量生成時,分別調(diào)用 alloc 和 init 方法,對象變量作用域結(jié)束時調(diào)用 objc_release 方法釋放對象變量,雖然 ARC 情況下不能使用 release 方法,但是由此可見編譯器編譯時在合適的地方插入了 release。
在使用 alloc、new、copy、mutableCopy 以外的方法生成對象變量方法時會有什么不同
{
id __strong object = [NSMutableArray array];
}
調(diào)用 array 的類方法轉(zhuǎn)換后:
{
/*編譯器的模擬代碼*/
id object = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(object);
objc_release(object);
}
objc_retainAutoreleasedReturnValue(object) 函數(shù)的作用:最優(yōu)化程序運行。
自己持有(retain)對象的函數(shù),但它持有的應(yīng)為返回注冊在 autoreleasepool 中對象的方法或函數(shù)的返回值。
objc_retainAutoreleasedReturnValue 函數(shù)與 objc_autoreleasedReturnValue 是成對出現(xiàn)的,現(xiàn)在看看 NSMutableArray 類的 array 類方法的編譯器實現(xiàn)。
+ (id)array {
return [[NSMutableArray alloc] init];
}
轉(zhuǎn)換后的源代碼。
+ (id)array
{
/*編譯器的模擬代碼*/
id obj = objc_msgSend(NSMutableArray, @selector(alloc));
objc_msgSend(obj, @selector(init));
return objc_autoreleaseReturnValue(obj);
}
通過 objc_autoreleaseReturnValue 函數(shù)將對象注冊在自動釋放池 autoreleasepool 中并返回,但是與 objc_autorelease 函數(shù)不同的是,objc_autoreleaseReturnValue 函數(shù)一般不僅限于注冊對象到 autoreleasepool 中去。
objc_autoreleaseReturnValue 與 objc_retainAutoreleasedReturnValue 的配合使用,可以不將對象注冊到autoreleasepool 中而直接傳遞,達到最優(yōu)化。
objc_autoreleaseReturnValue 函數(shù)會檢查使用該函數(shù)的方法或者函數(shù)的調(diào)用方的執(zhí)行命令列表,如果調(diào)用方在調(diào)用該函數(shù)或方法之后,緊接著調(diào)用了 objc_retainAutoreleasedReturnValue 函數(shù),那么不再將對象注冊到 autoreleasepool 中去,而直接將對象傳遞給調(diào)用方。
相比于 objc_retain 函數(shù)來說 objc_retainAutoreleasedReturnValue 函數(shù)在返回一個即使沒有注冊到 autoreleasepool 中的對象,也能正確的獲取對象。
3.2 __weak
弱引用。相當于 @property 的 "weak"。弱引用不會影響對象的引用計數(shù),即只要對象沒有任何強引用指向,即使有 n 個弱引用對象指向也沒用,該對象依然會被釋放。
對象在被釋放的同時,指向它的弱引用(weak)會自動被置 nil,這個技術(shù)叫 zeroing weak pointer。這樣有效的防止無效指針、野指針的產(chǎn)生。__weak 一般用在 delegate 關(guān)系中防止循環(huán)引用或者用來修飾指向由 Interface Builder 編輯與生成的 UI 控件。
{
id _weak object = [[NSObject alloc] init];
}
轉(zhuǎn)換后的模擬源代碼。
{
/* 編譯器的模擬代碼 */
id object;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_initWeak(&object, tmp);
objc_release(tmp);
objc_destoryWeak(&object);
}
自己生成并且持有的對象通過 objc_initWeak 函數(shù)賦值給 __weak 修飾符的變量,但是編譯器判斷并沒有對其進行持有,因此該對象通過 objc_release 函數(shù)被釋放和廢棄。
隨后通過 objc_destoryWeak 將引用廢棄對象的附有 __weak 修飾符的變量置為 nil。
如果不是直接賦值,而是通過使用 __weak 修飾符來引用變量時。
{
id __weak object = obj;
NSLog(@"%@", object);
}
轉(zhuǎn)換后的模擬源代碼。
/*編譯器的模擬代碼*/
{
id object;
objc_initWeak(&object, obj);
id temp = objc_loadWeakRetained(&object);
objc_autorelease(temp);
NSLog(@"%@", temp);
objc_destoryWeak(&object);
}
明顯增加了 objc_loadWeakRetained 與 objc_autorelease 函數(shù)調(diào)用,他們的主要作用是:
- objc_loadWeakRetained 函數(shù)取出 __weak 修飾符變量引用的對象并且 retain
- objc_autorelease 函數(shù)將引用的對象注冊到 autoreleasepool 中。
因此,使用 __weak 修飾符引用的對象都被注冊到 autoreleasepool 中,在 @autoreleasepool 塊結(jié)束之前都可以放心使用,大量使用 __weak 修飾符的變量,導致注冊到 autoreleasepool 中的對象也大量地增加。所以在使用 __weak 修飾符引用的變量時,最好先暫時用 __strong 修飾符的變量進行引用后再使用。
2 種不能使用 __weak 修飾符的情況:
- 重寫了 retain/release 的類,例如 NSMachPort 類;
- 當 allowsWeakReference/retainWeakReference 實例方法返回 NO 時。
3.3 __autoreleasing
對象被加入到 autorelease pool,是會自動釋放的引用,與 MRC 中 autorelease 的用法相同。定義 @property 時不能使用這個修飾符。
對于 alloc、new、copy、mutableCopy 的實現(xiàn)。
@autoreleasepool{
id __autoreleasing object = [[NSObject alloc] init];
}
轉(zhuǎn)換后的模擬源代碼。
{
/* 編譯器的模擬代碼 */
id pool = objc_autoreleasePoolPush();
id object = objc_msgSend(NSObjct, @selector(alloc));
objc_msgSend(object, @selector(init));
// 調(diào)用autorelease方法
objc_autorelease(object);
id pool = objc_autoreleasePoolPop();
}
NSMutableArray 類中的 array 方法如何實現(xiàn) autorelease 功能。
@autoreleasepool{
id __autoreleasing object = [NSMutableArray array];
}
轉(zhuǎn)化后的模擬源代碼。
{
/* 編譯器的模擬代碼 */
id pool = objc_autoreleasePoolPush();
id object = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(object);
// 調(diào)用 autorelease 方法
objc_autorelease(object);
id pool = objc_autoreleasePoolPop();
}
除了持有對象的方法從 alloc 變成了 objc_retainAutoreleasedReturnValue 函數(shù),但是注冊到 autoreleasepool 的方法沒有變化,都是調(diào)用了 objc_autorelease 函數(shù)。
一個常見的誤解是,在 ARC 中沒有 autorelease,因為這樣一個“自動釋放”看起來好像有點多余。
這個誤解可能源自于將 ARC 的“自動” 和 autorelease “自動” 的混淆。其實你只要看一下每個 iOS App 的 main.m 文件就能知道,autorelease 不僅好好的存在著,并且不需要再手工被創(chuàng)建,也不需要再顯式得調(diào)用 [pool drain] 方法釋放內(nèi)存池。
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
以下兩行代碼的意義是相同的。
NSString * str = [[[NSString alloc] initWithFormat:@"China"] autorelease]; // MRC
NSString * __autoreleasing str = [[NSString alloc] initWithFormat:@"China"]; // ARC
__autoreleasing 在 ARC 中主要用在參數(shù)傳遞返回值(out-parameters)和引用傳遞參數(shù)(pass-by-reference)的情況下。
__autoreleasing is used to denote arguments that are passed by reference (id *) and are autoreleased on return.
比如常見的 NSError 的使用:
NSError * __autoreleasing error;
// writeToFile方法中 error 參數(shù)的類型為 (NSError *__autoreleasing *))
if (![data writeToFile:filename options:NSDataWritingAtomic error:&error]) {
NSLog(@"Error: %@", error.localizedDescription);
}
注意:如果 error 的修飾符為 strong,那么,編譯器會幫你隱式地做如下事情,保證最終傳入函數(shù)的參數(shù)依然是個 __autoreleasing 類型的引用。
NSError * error;
NSError * __autoreleasing tempError = error; // 編譯器添加
if (![data writeToFile:filename options:NSDataWritingAtomic error:&tempError])
{
error = tempError; // 編譯器添加
NSLog(@"Error: %@", error.localizedDescription);
}
為了避免這種情況,提高效率,一般在定義 error 的時候?qū)⑵渎暶鳛開_autoreleasing 類型的:
NSError *__autoreleasing error;
加上 __autoreleasing 之后,相當于在 MRC 中對返回值 error 做了如下事情:
*error = [[[NSError alloc] init] autorelease];
*error 指向的對象在創(chuàng)建出來后,被放入到了 autoreleasing pool 中,等待使用結(jié)束后的自動釋放,函數(shù)外 error 的使用者并不需要關(guān)心 *error 指向?qū)ο蟮尼尫拧?/p>
另外,在 ARC 中,所有這種指針的指針(NSError **)的函數(shù)參數(shù)如果不加修飾符,編譯器會默認將他們認定為 __autoreleasing 類型。
比如下面的兩段代碼是等同的:
- (NSString *)doSomething:(NSNumber **)value
{
// do something
}
- (NSString *)doSomething:(NSNumber * __autoreleasing *)value
{
// do something
}
除非顯式得給 value 聲明了 __strong,否則 value 默認就是 __autoreleasing 的。
最后一點,某些類的方法會隱式地使用自己的 autorelease pool,在這種時候使用 __autoreleasing 類型要特別小心。
比如 NSDictionary 的 - enumerateKeysAndObjectsUsingBlock: 方法會隱式地創(chuàng)建一個 autorelease pool.
- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){
// do stuff
if (...) {
*error = [NSError errorWithDomain:@"Not Found" code:404 userInfo:nil];
}
}];
}
上面代碼實際類似于:
- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){
@autoreleasepool // 被隱式創(chuàng)建
{
if (...) {
*error = [NSError errorWithDomain:@"Not Found" code:404 userInfo:nil];
}
}
}];
// *error 在這里已經(jīng)被dict的做枚舉遍歷時創(chuàng)建的 autorelease pool 釋放掉了
}
為了能夠正常的使用 *error,我們需要一個 strong 型的臨時引用,在 dict 的枚舉 block 中使用這個臨時引用,保證引用指向的對象不會在出了 dict 的枚舉 block 后被釋放,正確的方式如下:
- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
__block NSError * tempError; // 加 __block 保證可以在 Block 內(nèi)被修改
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if (...) {
*tempError = [NSError errorWithDomain:@"Not Found" code:404 userInfo:nil];
}
}]
if (error != nil)
{
*error = tempError;
}
}
3.4 __unsafe_unretained
ARC 是在 iOS 5 引入的,而這個修飾符主要是為了在 ARC 剛發(fā)布時兼容 iOS 4 以及版本更低的設(shè)備,因為這些低版本的設(shè)備沒有 weak pointer system,這個系統(tǒng)簡單的理解就是上面講 weak 時提到的,能夠在 weak 引用指向?qū)ο蟊会尫藕?,把引用值自動設(shè)為 nil。
相當于 @property 的 "unsafe_unretained",實際可以將它理解為 MRC 時代的 assign:純粹只是將引用指向?qū)ο?,沒有任何額外的操作,在指向?qū)ο蟊会尫艜r依然指向原來被釋放的對象(所在的內(nèi)存區(qū)域)。所以非常不安全。
現(xiàn)在可以完全忽略掉這個修飾符了,因為 iOS 4 早已退出歷史舞臺很多年。
{
id __unsafe_unretained object = [[NSObject alloc] init];
}
轉(zhuǎn)換后的模擬源代碼。
{
/*編譯器的模擬代碼*/
id object = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(object, @selector(init));
objc_release(tmp);
}
可見通過 __unsafe_unretained 修飾的變量引用了對象但是并不持有對象,對象在釋放和廢棄后,并沒有調(diào)用被 __unsafe_unretained 修飾的變量的 objc_destoryWeak 函數(shù),因此該對象的懸垂指針被賦值給變量 object,導致引用變量 object 時發(fā)生崩潰。
3.5 正確使用修飾符
蘋果的文檔中明確地寫道:
You should decorate variables correctly. When using qualifiers in an object variable declaration,
the correct format is:
??ClassName * qualifier variableName;
按照這個說明,要定義一個 weak 修飾的 NSString 引用,它的寫法應(yīng)該是:
NSString * __weak str = @"Hello"; // 正確
而不應(yīng)該是:
__weak NSString *str = @"Hello"; // 錯誤
那這里就有疑問了,既然文檔說是錯誤的,為啥編譯器不報錯呢?文檔又解釋道:
Other variants are technically incorrect but are "forgiven" by the compiler. To understand the issue, see http://cdecl.org/.
看來是蘋果考慮到很多人會用錯,所以在編譯器這邊貼心地幫我們忽略并處理掉了這個錯誤。雖然不報錯,但是我們還是應(yīng)該按照正確的方式去使用這些修飾符。
3.6 棧中指針默認值為 nil
無論是被 strong、weak 還是 autoreleasing 修飾,聲明在棧中的指針默認值都會是 nil。所有這類型的指針不用再初始化的時候置 nil 了。這個特性更加降低了“野指針”出現(xiàn)的可能性。
在 ARC 中,以下代碼會輸出 null 而不是 crash。
- (void)myMethod
{
NSString * name;
NSLog(@"%@", name);
}
四、ARC 與 Block
在手動管理內(nèi)存時代,block 會隱式地對進入其作用域內(nèi)的對象(或者說被 block 捕獲的指針指向的對象)執(zhí)行 retain 操作,來確保 block 使用到該對象時,能夠正確的訪問。
MyViewController * myController = [[MyViewController alloc] init…];
myController.dismissBlock = ^(NSString * result) {
// 隱式地調(diào)用 [myController retain]; 造成循環(huán)引用
[myController dismissViewControllerAnimated:YES completion:nil];
};
[self presentViewController:myController animated:YES completion:^{
// 調(diào)用[myController release];是在 MRC 中的一個常規(guī)寫法,并不能解決上面循環(huán)引用的問題
[myController release];
}];
@interface SecondVC : UIViewController
@property (nonatomic, copy) void (^ block)(void);
@end
{
SecondVC * vc = [[SecondVC alloc] init];
NSLog(@"%lu", (unsigned long)vc.retainCount);
vc.block = ^ {
NSLog(@"%lu", (unsigned long)vc.retainCount);
};
vc.block();
}
2018-11-16 10:26:05.872092+0800 Demo[49289:1083433] 1
2018-11-16 10:26:05.872214+0800 Demo[49289:1083433] 2
dismissBlock 調(diào)用了 [myController dismiss..] 方法,這時 dismissBlock 會對 myController 執(zhí)行 retain 操作。
而作為 myController 的屬性,myController 對 dismissBlock 也至少有一個 retain(一般準確講是 copy),這時就出現(xiàn)了在內(nèi)存管理中最糟糕的情況:循環(huán)引用。也就是說:相互持有對方。循環(huán)引用導致了 myController 和 dismissBlock 最終都不能被釋放。
對 delegate 指針用 weak 就是為了避免這種問題。
不過好在,編譯器會及時地給我們一個警告,提醒我們可能會發(fā)生這類型的問題:

我們一般用如下方法解決:給進入 block 的指針加一個 __block 修飾符。
這個 __block 在 MRC 時代有兩個作用:
- 說明變量可改
- 說明指針指向的對象不做隱式的 retain 操作
除了靜態(tài)變量和全局變量不需要加 __block 就可以在 block 中修改外,其他變量不加則不能在 block 中修改。
對代碼做出修改,解決了循環(huán)引用的問題:
MyViewController * __block myController = [[MyViewController alloc] init…];
myController.dismissBlock = ^(NSString * result) {
[myController dismissViewControllerAnimated:YES completion:nil];
};
// 之后正常的 release 或者 retain
在 ARC 環(huán)境下,沒有了 retain 和 release 等操作,情況也發(fā)生了改變:
在任何情況下,__block 修飾符的作用只有上面的第一條:說明變量可改。即使加上了 __block 修飾符,一個被 block 捕獲的強引用也依然是一個強引用。
所以在 ARC 下,如果還按照 MRC 下的寫法,添加 __block 是沒有解決循環(huán)引用的問題。
代碼修改如下:
__block MyViewController * myController = [[MyViewController alloc] init…];
myController.dismissBlock = ^(NSString * result) {
[myController dismissViewControllerAnimated:YES completion:nil];
myController = nil; // 注意這里,保證了 block 結(jié)束對 myController 強引用的解除
};
在 block 中將捕獲的指針置為 nil,保證了 dismissBlock 對 myController 強引用的解除,不過也同時解除了myController 指針對 myController 對象的強引用。
更好的方法就是使用 weak。(或者為了考慮 iOS4 的兼容性用 unsafe_unretained,具體用法和 weak 相同)
為了保證 dismissBlock 對 myController 沒有強引用,我們可以定義一個臨時的弱引用 weakMyViewController 來指向原myController 的對象,并把這個弱引用傳入到 dismissBlock 內(nèi),這樣就保證了 dismissBlock 對 myController 持有的是一個弱引用,而不是一個強引用。如此,繼續(xù)修改代碼如下:
MyViewController * __weak weakMyViewController = myController;
myController.dismissBlock = ^(NSString * result) {
[weakMyViewController dismissViewControllerAnimated:YES completion:nil];
};
這樣循環(huán)引用的問題就解決了,但是卻引入了一個新的問題:由于傳入 dismissBlock 的是一個弱引用,那么當 myController指向的對象在 dismissBlock 被調(diào)用前釋放,那么 dismissBlock 就不能正常的運作了。在一般的單線程環(huán)境中,這種問題出現(xiàn)的可能性不大,但是到了多線程環(huán)境,就很不好說了,所以我們需要繼續(xù)完善這個方法。
為了保證在 dismissBlock 內(nèi)能夠訪問到正確的 myController,我們在 dismissBlock 內(nèi)新定義一個強引用strongMyController 來指向 weakMyController 指向的對象,這樣多了一個強引用,就能保證這個 myController 對象不會在 dismissBlock 被調(diào)用前釋放掉了。于是,對代碼再次做出修改:
MyViewController * __weak weakMyController = myController;
// __weak typeof(myController) weakMyController = myController;
myController.dismissBlock = ^(NSString * result) {
MyViewController * strongMyController = weakMyController;
// __strong typeof(weakMyController) strongMyController = weakMyController;
if (strongMyController) {
[strongMyController dismissViewControllerAnimated:YES completion:nil];
}
else {
}
};
很多讀者會有疑問,不是不希望 block 對原 myController 對象增加強引用么,這里為什么堂而皇之地在 block 內(nèi)新定義了一個強引用,這個強引用不會造成循環(huán)引用么?
理解這個問題的關(guān)鍵在于被 block 捕獲的引用和在 block 內(nèi)定義的引用的區(qū)別。為了搞得明白這個問題,這里需要了解一些Block 的實現(xiàn)原理,詳細的內(nèi)容可以參考其他的文章:談Objective-C block的實現(xiàn)、block 實現(xiàn)、正確使用Block避免Cycle Retain和Crash。
為了更清楚地說明問題,這里用一個簡單的程序舉例。如下程序:
#include <stdio.h>
int main()
{
int b = 10;
int *a = &b;
void (^ block)() = ^() {
int *c = a;
};
block();
return 1;
}
程序中,同為 int 型的指針,a 變量被 block 捕獲,而 c 變量是在 block 內(nèi)定義的。用 clang -rewrite-objc 命令處理后,可以看到如下代碼。
原 main 函數(shù):
int main()
{
int b = 10;
int *a = &b;
void (*block)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a);
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 1;
}
block 的結(jié)構(gòu):
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *a; // 被捕獲的引用 a 出現(xiàn)在了 block 的結(jié)構(gòu)體里面
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
實際執(zhí)行的函數(shù):
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *a = __cself->a; // bound by copy
int *c = a; // 在 block 中聲明的引用 c 在函數(shù)中聲明,存在于函數(shù)棧上
}
可以清楚的看到,a 和 c 存在的位置完全不同,如果 block 存在于堆上(在 ARC 下 block 默認在堆上),那么 a 作為 block 結(jié)構(gòu)體的一個成員,也自然會存在于堆上,而 c 無論如何,永遠位于 block 內(nèi)實際執(zhí)行代碼的函數(shù)棧內(nèi)。這也導致了兩個變量生命周期的完全不同:c 在 block 的函數(shù)運行完畢,即會被釋放,而 a 只有在 block 被從堆上釋放的時候才會釋放。
回到之前的示例,如果直接讓 dismissBlock 捕獲 myController 引用,那么這個引用會被復制后作為 dismissBlock 的成員變量存在于其所在的堆空間中,也就是為 dismissBlock 增加了一個指向 myController 對象的強引用,這就是造成循環(huán)引用的本質(zhì)原因。
對于 MyViewController 的例子,dismissBlock 的結(jié)構(gòu)體大概是這個樣子:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
MyViewController * __strong myController; // 被捕獲的強引用 myController
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
而給 dismissBlock 傳入一個弱引用 weakMyController,這時 dismissBlock 的結(jié)構(gòu):
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
MyViewController * __weak weakMyController; // 被捕獲的弱引用 weakMyController
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
在 dismissBlock 內(nèi)聲明的強引用 strongMyController,它雖然是強引用,但存在于函數(shù)棧中,在函數(shù)執(zhí)行期間,它一直存在,一直持有 myController 對象,但當函數(shù)執(zhí)行完畢,strongMyController 即被銷毀,于是它對 myController 對象的強引用被解除,這時 dismissBlock 對 myController 對象就不存在強引用關(guān)系了!
加入了 strongMyController 的函數(shù)大體會是這個樣子:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
MyViewController * __strong strongMyController = __cself->weakMyController;
}
在 ARC 中,block 捕獲的引用和 block 內(nèi)聲明的引用,存儲空間與生命周期都是不同的。
實際上,在自動引用計數(shù)環(huán)境下,對 block 捕獲對象的內(nèi)存管理已經(jīng)簡化了很多,由于沒有了 retain 和 release 等操作,實際只需要考慮循環(huán)引用的問題就行了。
五、ARC 與 Toll-Free Bridging
There are a number of data types in the Core Foundation framework and the Foundation framework that can be used interchangeably. This capability, called toll-free bridging, means that you can use the same data type as the parameter to a Core Foundation function call or as the receiver of an Objective-C message.
Toll-Free Briding 保證了在程序中,可以方便和諧的使用 Core Foundation 類型的對象和 Objective-C 類型的對象。詳細的內(nèi)容可參考官方文檔。以下是官方文檔中給出的示例:
NSLocale * gbNSLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_GB"];
CFLocaleRef gbCFLocale = (CFLocaleRef) gbNSLocale;
CFStringRef cfIdentifier = CFLocaleGetIdentifier (gbCFLocale);
NSLog(@"cfIdentifier: %@", (NSString *)cfIdentifier); // logs: "cfIdentifier: en_GB"
CFRelease((CFLocaleRef) gbNSLocale);
CFLocaleRef myCFLocale = CFLocaleCopyCurrent();
NSLocale * myNSLocale = (NSLocale *) myCFLocale;
[myNSLocale autorelease];
NSString * nsIdentifier = [myNSLocale localeIdentifier];
CFShow((CFStringRef) [@"nsIdentifier: " stringByAppendingString:nsIdentifier]); // logs identifier for current locale
在 MRC 時代,由于 Objective-C 類型的對象和 Core Foundation 類型的對象都是相同的 retain 和 release 操作規(guī)則,所以Toll-Free Bridging 的使用比較簡單,但是自從 ARC 加入后,Objective-C 類型的對象內(nèi)存管理規(guī)則改變了,而 Core Foundation 依然是之前的機制,換句話說,Core Foundation 不支持 ARC。
這個時候就必須要考慮一個問題,在做 Core Foundation 與 Objective-C 類型轉(zhuǎn)換的時候,用哪一種規(guī)則來管理對象的內(nèi)存。顯然,對于同一個對象,我們不能夠同時用兩種規(guī)則來管理,所以這里就必須要確定一件事情:哪些對象用 Objective-C(也就是ARC)的規(guī)則,哪些對象用 Core Foundation 的規(guī)則(也就是 MRC)的規(guī)則?;蛘哒f要確定對象類型轉(zhuǎn)換了之后,內(nèi)存管理的ownership 的改變。
If you cast between Objective-C and Core Foundation-style objects, you need to tell the compiler about the ownership semantics of the object using either a cast (defined in objc/runtime.h) or a Core Foundation-style macro (defined in NSObject.h)
于是蘋果在引入 ARC 之后對 Toll-Free Bridging 的操作也加入了對應(yīng)的方法與修飾符,用來指明用哪種規(guī)則管理內(nèi)存,或者說是內(nèi)存管理權(quán)的歸屬。
5.1 __bridge
只是聲明類型轉(zhuǎn)變,但是不做內(nèi)存管理規(guī)則的轉(zhuǎn)變。
示例:
CFStringRef s = (__bridge CFStringRef)[[NSString alloc] initWithFormat:@"Hi, %@!", name];
只是 NSString 到 CFStringRef 的類型轉(zhuǎn)化,但管理規(guī)則未變,依然要用 Objective-C 類型的 ARC 來管理 s,不能用CFRelease() 去釋放 s。
5.2 __bridge_retained、CFBridgingRetain()
將指針類型轉(zhuǎn)變的同時,將內(nèi)存管理的責任由原來的 Objective-C 交給 Core Foundation 來處理,也就是,將 ARC 轉(zhuǎn)變?yōu)?MRC。
示例:
NSString * s1 = [[NSString alloc] initWithFormat:@"Hi, %@!", name];
CFStringRef s2 = (__bridge_retained CFStringRef)s1;
...
CFRelease(s2); // 注意在使用結(jié)束后釋放
在第二行做了轉(zhuǎn)化,這時內(nèi)存管理規(guī)則由 ARC 變成了 MRC,需要手動的來管理 s2 的內(nèi)存,而對于 s1,即使將其置為 nil,也不能釋放內(nèi)存。
也可以寫成:
NSString * s1 = [[NSString alloc] initWithFormat:@"Hi, %@!", name];
CFStringRef s2 = (CFStringRef)CFBridgingRetain(s1);
...
CFRelease(s2); // 注意在使用結(jié)束后釋放
5.3 __bridge_transfer、CFBridgingRelease()
功能與 __bridge_retained 相反,表示將管理的責任由 Core Foundation 轉(zhuǎn)交給 Objective-C,即將管理方式由MRC 轉(zhuǎn)變?yōu)?ARC。
比如:
CFStringRef result = CFURLCreateStringByAddingPercentEscapes(. . .);
NSString * s = (__bridge_transfer NSString *)result;
// 或 NSString * s = (NSString *)CFBridgingRelease(result);
return s;
這里將 result 的管理責任交給了 ARC 來處理,就不需要再顯式地調(diào)用 CFRelease() 了。
這里和 ARC 中 4 個主要的修飾符 __strong、__weak、__autoreleasing... 不同,這里修飾符的位置是放在類型前面的,雖然官方文檔中沒有說明,但最好與官方的相同。

六、ARC下獲取引用計數(shù)
6.1 使用 KVC
[obj valueForKey:@"retainCount"];
6.2 使用私有 API
OBJC_EXTERN int _objc_rootRetainCount(id);
_objc_rootRetainCount(obj);
這個不一定完全可信。Xcode 10.1 用的示例一直返回 1。
6.3 使用 CFGetRetainCount
CFGetRetainCount((__bridge CFTypeRef)(obj))
使用 Toll-Free-Bridging 將 OC 對象的內(nèi)容管理轉(zhuǎn)為 Core Foundation 對象。
七、學習文章
iOS 開發(fā)ARC內(nèi)存管理技術(shù)要點
談 Objective-C block的實現(xiàn)
block 的實現(xiàn)
正確使用 Block 避免Cycle Retain和Crash
ARC 的實現(xiàn)原理