iOS開發(fā)ARC內(nèi)存管理技術(shù)要點(diǎn)

本文來(lái)源于我個(gè)人的ARC學(xué)習(xí)筆記简烘,旨在通過(guò)簡(jiǎn)明扼要的方式總結(jié)出iOS開發(fā)中ARC(Automatic Reference Counting,自動(dòng)引用計(jì)數(shù))內(nèi)存管理技術(shù)的要點(diǎn)嘴秸,所以不會(huì)涉及全部細(xì)節(jié)措嵌。這篇文章不是一篇標(biāo)準(zhǔn)的ARC使用教程,并假定讀者已經(jīng)對(duì)ARC有了一定了解和使用經(jīng)驗(yàn)蔬捷。詳細(xì)的關(guān)于ARC的信息請(qǐng)參見蘋果的官方文檔與網(wǎng)上的其他教程:)
本文的主要內(nèi)容:
ARC的本質(zhì)
ARC的開啟與關(guān)閉
ARC的修飾符
ARC與Block
ARC與Toll-Free Bridging

一、ARC的本質(zhì):
ARC是編譯器(時(shí))特性榔袋,而不是運(yùn)行時(shí)特性周拐,更不是垃圾回收器(GC)。
Automatic Reference Counting (ARC) is a compiler-level feature that simplifies the process of managing object lifetimes (memory management) in Cocoa applications.

ARC只是相對(duì)于MRC(Manual Reference Counting或稱為非ARC凰兑,下文中我們會(huì)一直使用MRC來(lái)指代非ARC的管理方式)的一次改進(jìn)妥粟,但它和之前的技術(shù)本質(zhì)上沒有區(qū)別。具體信息可以參考ARC編譯器官方文檔吏够。

二勾给、ARC的開啟與關(guān)閉
不同于XCode4可以在創(chuàng)建工程時(shí)選擇關(guān)閉ARC滩报,XCode5在創(chuàng)建的工程是默認(rèn)開啟ARC,沒有可以關(guān)閉ARC的選項(xiàng)播急。
如果需要對(duì)特定文件開啟或關(guān)閉ARC脓钾,可以在工程選項(xiàng)中選擇Targets -> Compile Phases -> Compile Sources,在里面找到對(duì)應(yīng)文件桩警,添加flag:
打開ARC:-fobjc-arc

關(guān)閉ARC:-fno-objc-arc
如圖:


三可训、ARC的修飾符
ARC主要提供了4種修飾符,他們分別是:__strong,__weak,__autoreleasing,__unsafe_unretained捶枢。
__strong
表示引用為強(qiáng)引用握截。對(duì)應(yīng)在定義property時(shí)的"strong"。所有對(duì)象只有當(dāng)沒有任何一個(gè)強(qiáng)引用指向時(shí)柱蟀,才會(huì)被釋放川蒙。
注意:如果在聲明引用時(shí)不加修飾符蚜厉,那么引用將默認(rèn)是強(qiáng)引用长已。當(dāng)需要釋放強(qiáng)引用指向的對(duì)象時(shí),需要將強(qiáng)引用置nil昼牛。
__weak
表示引用為弱引用术瓮。對(duì)應(yīng)在定義property時(shí)用的"weak"。弱引用不會(huì)影響對(duì)象的釋放贰健,即只要對(duì)象沒有任何強(qiáng)引用指向胞四,即使有100個(gè)弱引用對(duì)象指向也沒用,該對(duì)象依然會(huì)被釋放伶椿。不過(guò)好在辜伟,對(duì)象在被釋放的同時(shí),指向它的弱引用會(huì)自動(dòng)被置nil脊另,這個(gè)技術(shù)叫zeroing weak pointer导狡。這樣有效得防止無(wú)效指針、野指針的產(chǎn)生偎痛。__weak一般用在delegate關(guān)系中防止循環(huán)引用或者用來(lái)修飾指向由Interface Builder編輯與生成的UI控件旱捧。

____autoreleasing
表示在autorelease pool中自動(dòng)釋放對(duì)象的引用,和MRC時(shí)代autorelease的用法相同踩麦。定義property時(shí)不能使用這個(gè)修飾符枚赡,任何一個(gè)對(duì)象的property都不應(yīng)該是autorelease型的。
一個(gè)常見的誤解是谓谦,在ARC中沒有autorelease贫橙,因?yàn)檫@樣一個(gè)“自動(dòng)釋放”看起來(lái)好像有點(diǎn)多余。這個(gè)誤解可能源自于將ARC的“自動(dòng)”和autorelease“自動(dòng)”的混淆反粥。其實(shí)你只要看一下每個(gè)iOS App的main.m文件就能知道卢肃,autorelease不僅好好的存在著谓松,并且變得更fashion了:不需要再手工被創(chuàng)建,也不需要再顯式得調(diào)用[drain]方法釋放內(nèi)存池践剂。



以下兩行代碼的意義是相同的鬼譬。

NSString *str = [[[NSString alloc] initWithFormat:@"hehe"] autorelease]; // MRC 
NSString *__autoreleasing str = [[NSString alloc] initWithFormat:@"hehe"]; // ARC

這里關(guān)于autoreleasepool就不做展開了,詳細(xì)地信息可以參考官方文檔或者其他文章逊脯。
__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;
if (![data writeToFile:filename options:NSDataWritingAtomic error:&error]) 
{  NSLog(@"Error: %@", error);}

(在上面的writeToFile方法中error參數(shù)的類型為**(NSError ***____autoreleasing *))
注意,如果你的error定義為了strong型军洼,那么巩螃,編譯器會(huì)幫你隱式地做如下事情,保證最終傳入函數(shù)的參數(shù)依然是個(gè)__autoreleasing類型的引用匕争。

NSError *error; NSError *__autoreleasing tempError = error; // 編譯器添加 
if (![data writeToFile:filename options:NSDataWritingAtomic error:&tempError]) 
{  error = tempError; // 編譯器添加   NSLog(@"Error: %@", error);}

所以為了提高效率避乏,避免這種情況,我們一般在定義error的時(shí)候?qū)⑵洌ɡ侠蠈?shí)實(shí)地=甘桑。=)聲明為__autoreleasing類型的:
NSError *__autoreleasing error;

在這里拍皮,加上__autoreleasing之后,相當(dāng)于在MRC中對(duì)返回值error做了如下事情:
*error = [[[NSError alloc] init] autorelease];

****error指向的對(duì)象在創(chuàng)建出來(lái)后跑杭,被放入到了autoreleasing pool中铆帽,等待使用結(jié)束后的自動(dòng)釋放,函數(shù)外error的使用者并不需要關(guān)心*error指向?qū)ο蟮尼尫拧?br> 另外一點(diǎn)德谅,在ARC中爹橱,所有這種指針的指針 (NSError **)的函數(shù)參數(shù)如果不加修飾符,編譯器會(huì)默認(rèn)將他們認(rèn)定為__autoreleasing類型窄做。
比如下面的兩段代碼是等同的:

- (NSString *)doSomething:(NSNumber **)value{ // do something }
- (NSString *)doSomething:(NSNumber * __autoreleasing *)value{ // do something }

除非你顯式得給value聲明了__strong愧驱,否則value默認(rèn)就是__autoreleasing的。
最后一點(diǎn)椭盏,某些類的方法會(huì)隱式地使用自己的autorelease pool组砚,在這種時(shí)候使用__autoreleasing類型要特別小心。
比如NSDictionary的[enumerateKeysAndObjectsUsingBlock]方法:

- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
    [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){

          // do stuff  
          if (there is some error && error != nil)
          {
                *error = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil];
          }

    }];
}

會(huì)隱式地創(chuàng)建一個(gè)autorelease pool庸汗,上面代碼實(shí)際類似于:

- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
    [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){

          @autoreleasepool  // 被隱式創(chuàng)建
      {
              if (there is some error && error != nil)
              {
                    *error = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil];
              }
          }
    }];

    // *error 在這里已經(jīng)被dict的做枚舉遍歷時(shí)創(chuàng)建的autorelease pool釋放掉了 :(  
}

為了能夠正常的使用*error惫确,我們需要一個(gè)strong型的臨時(shí)引用,在dict的枚舉Block中是用這個(gè)臨時(shí)引用蚯舱,保證引用指向的對(duì)象不會(huì)在出了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 (there is some error) 
    { 
      *tempError = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil]; 
    }  

  }] 

  if (error != nil) 
  { 
    *error = tempError; 
  } 
}

__unsafe_unretained
ARC是在iOS 5引入的,而這個(gè)修飾符主要是為了在ARC剛發(fā)布時(shí)兼容iOS 4以及版本更低的設(shè)備枉昏,因?yàn)檫@些版本的設(shè)備沒有weak pointer system陈肛,簡(jiǎn)單的理解這個(gè)系統(tǒng)就是我們上面講weak時(shí)提到的,能夠在weak引用指向?qū)ο蟊会尫藕笮至眩岩弥底詣?dòng)設(shè)為nil的系統(tǒng)句旱。這個(gè)修飾符在定義property時(shí)對(duì)應(yīng)的是"unsafe_unretained"阳藻,實(shí)際可以將它理解為MRC時(shí)代的assign:純粹只是將引用指向?qū)ο螅瑳]有任何額外的操作谈撒,在指向?qū)ο蟊会尫艜r(shí)依然原原本本地指向原來(lái)被釋放的對(duì)象(所在的內(nèi)存區(qū)域)腥泥。所以非常不安全。
現(xiàn)在可以完全忽略掉這個(gè)修飾符了啃匿,因?yàn)閕OS 4早已退出歷史舞臺(tái)很多年蛔外。

*使用修飾符的正確姿勢(shì)(方式=。=)
這可能是很多人都不知道的一個(gè)問題溯乒,包括之前的我夹厌,但卻是一個(gè)特別要注意的問題。
蘋果的文檔中明確地寫道:

You should decorate variables correctly. When using qualifiers in an object variable declaration,

the correct format is:

ClassName * qualifier variableName;

按照這個(gè)說(shuō)明裆悄,要定義一個(gè)weak型的NSString引用矛纹,它的寫法應(yīng)該是:

NSString * __weak str = @"hehe"; // 正確!

而不應(yīng)該是:

__weak NSString *str = @"hehe"; // 錯(cuò)誤光稼!

我相信很多人都和我一樣或南,從開始用ARC就一直用上面那種錯(cuò)誤的寫法。
那這里就有疑問了钟哥,既然文檔說(shuō)是錯(cuò)誤的迎献,為啥編譯器不報(bào)錯(cuò)呢?文檔又解釋道:
Other variants are technically incorrect but are “forgiven” by the compiler. To understand the issue, see[http://cdecl.org/](http://cdecl.org/).
好吧腻贰,看來(lái)是蘋果爸爸(=。=)考慮到很多人會(huì)用錯(cuò)扒秸,所以在編譯器這邊貼心地幫我們忽略并處理掉了這個(gè)錯(cuò)誤:)雖然不報(bào)錯(cuò)播演,但是我們還是應(yīng)該按照正確的方式去使用這些修飾符,如果你以前也常常用錯(cuò)誤的寫法伴奥,那看到這里記得以后不要這么寫了写烤,哪天編譯器怒了,再不支持錯(cuò)誤的寫法拾徙,就要郁悶了洲炊。
棧中指針默認(rèn)值為nil
無(wú)論是被strong,weak還是autoreleasing修飾尼啡,聲明在棧中的指針默認(rèn)值都會(huì)是nil暂衡。所有這類型的指針不用再初始化的時(shí)候置nil了。雖然好習(xí)慣是最重要的崖瞭,但是這個(gè)特性更加降低了“野指針”出現(xiàn)的可能性狂巢。
在ARC中,以下代碼會(huì)輸出null而不是crash:)

- (void)myMethod 
{
    NSString *name; NSLog(@"name: %@", name);
}

四书聚、ARC與Block
在MRC時(shí)代唧领,Block會(huì)隱式地對(duì)進(jìn)入其作用域內(nèi)的對(duì)象(或者說(shuō)被Block捕獲的指針指向的對(duì)象)加retain藻雌,來(lái)確保Block使用到該對(duì)象時(shí),能夠正確的訪問斩个。
這件事情在下面代碼展示的情況中要更加額外小心胯杭。

MyViewController *myController = [[MyViewController alloc] init…];

// 隱式地調(diào)用[myController retain];造成循環(huán)引用
myController.completionHandler =  ^(NSInteger result) {
   [myController dismissViewControllerAnimated:YES completion:nil];
};

[self presentViewController:myController animated:YES completion:^{
   [myController release]; // 注意,這里調(diào)用[myController release];是在MRC中的一個(gè)常規(guī)寫法受啥,并不能解決上面循環(huán)引用的問題
}];

在這段代碼中歉摧,myController的completionHandler調(diào)用了myController的方法[dismissViewController...],這時(shí)completionHandler會(huì)對(duì)myController做retain操作腔呜。而我們知道叁温,myController對(duì)completionHandler也至少有一個(gè)retain(一般準(zhǔn)確講是copy),這時(shí)就出現(xiàn)了在內(nèi)存管理中最糟糕的情況:循環(huán)引用核畴!簡(jiǎn)單點(diǎn)說(shuō)就是:myController retain了completionHandler膝但,而completionHandler也retain了myController。循環(huán)引用導(dǎo)致了myController和completionHandler最終都不能被釋放谤草。我們?cè)赿elegate關(guān)系中跟束,對(duì)delegate指針用weak就是為了避免這種問題。
不過(guò)好在丑孩,編譯器會(huì)及時(shí)地給我們一個(gè)警告冀宴,提醒我們可能會(huì)發(fā)生這類型的問題:



對(duì)這種情況,我們一般用如下方法解決:給要進(jìn)入Block的指針加一個(gè)__block修飾符温学。
這個(gè)__block在MRC時(shí)代有兩個(gè)作用:
說(shuō)明變量可改
說(shuō)明指針指向的對(duì)象不做這個(gè)隱式的retain操作

一個(gè)變量如果不加__block略贮,是不能在Block里面修改的,不過(guò)這里有一個(gè)例外:static的變量和全局變量不需要加__block就可以在Block中修改仗岖。
使用這種方法逃延,我們對(duì)代碼做出修改,解決了循環(huán)引用的問題:

MyViewController * __block myController = [[MyViewController alloc] init…];
// ...myController.completionHandler = ^(NSInteger result) { 
[myController dismissViewControllerAnimated:YES completion:nil];
};//之后正常的release或者retain

在ARC引入后轧拄,沒有了retain和release等操作揽祥,情況也發(fā)生了改變:在任何情況下,__block修飾符的作用只有上面的第一條:說(shuō)明變量可改檩电。即使加上了__block修飾符拄丰,一個(gè)被block捕獲的強(qiáng)引用也依然是一個(gè)強(qiáng)引用。這樣在ARC下俐末,如果我們還按照MRC下的寫法料按,completionHandler對(duì)myController有一個(gè)強(qiáng)引用,而myController對(duì)completionHandler有一個(gè)強(qiáng)引用鹅搪,這依然是循環(huán)引用站绪,沒有解決問題:(
于是我們還需要對(duì)原代碼做修改。簡(jiǎn)單的情況我們可以這樣寫:

__block MyViewController * myController = [[MyViewController alloc] init…];
// ...myController.completionHandler = ^(NSInteger result) { 
[myController dismissViewControllerAnimated:YES completion:nil]; 
myController = nil; // 注意這里丽柿,保證了block結(jié)束myController強(qiáng)引用的解除};

在completionHandler之后將myController指針置nil恢准,保證了completionHandler對(duì)myController強(qiáng)引用的解除魂挂,不過(guò)也同時(shí)解除了myController對(duì)myController對(duì)象的強(qiáng)引用。這種方法過(guò)于簡(jiǎn)單粗暴了馁筐,在大多數(shù)情況下涂召,我們有更好的方法。
這個(gè)更好的方法就是使用weak敏沉。(或者為了考慮iOS4的兼容性用unsafe_unretained果正,具體用法和weak相同,考慮到現(xiàn)在iOS4設(shè)備可能已經(jīng)絕跡了盟迟,這里就不講這個(gè)方法了)(關(guān)于這個(gè)方法的本質(zhì)我們后面會(huì)談到)
為了保證completionHandler這個(gè)Block對(duì)myController沒有強(qiáng)引用秋泳,我們可以定義一個(gè)臨時(shí)的弱引用weakMyViewController來(lái)指向原myController的對(duì)象,并把這個(gè)弱引用傳入到Block內(nèi)攒菠,這樣就保證了Block對(duì)myController持有的是一個(gè)弱引用迫皱,而不是一個(gè)強(qiáng)引用。如此辖众,我們繼續(xù)修改代碼:

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

這樣循環(huán)引用的問題就解決了卓起,但是卻不幸地引入了一個(gè)新的問題:由于傳入completionHandler的是一個(gè)弱引用,那么當(dāng)myController指向的對(duì)象在completionHandler被調(diào)用前釋放凹炸,那么completionHandler就不能正常的運(yùn)作了戏阅。在一般的單線程環(huán)境中,這種問題出現(xiàn)的可能性不大啤它,但是到了多線程環(huán)境奕筐,就很不好說(shuō)了,所以我們需要繼續(xù)完善這個(gè)方法蚕键。
為了保證在Block內(nèi)能夠訪問到正確的myController救欧,我們?cè)赽lock內(nèi)新定義一個(gè)強(qiáng)引用strongMyController來(lái)指向weakMyController指向的對(duì)象,這樣多了一個(gè)強(qiáng)引用锣光,就能保證這個(gè)myController對(duì)象不會(huì)在completionHandler被調(diào)用前釋放掉了。于是铝耻,我們對(duì)代碼再次做出修改:

MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyController = myController;
myController.completionHandler =  ^(NSInteger result) {
    MyViewController *strongMyController = weakMyController;

  if (strongMyController) {
        // ...
        [strongMyController dismissViewControllerAnimated:YES completion:nil];
        // ...
    }
    else {
        // Probably nothing...
    }
};

到此誊爹,一個(gè)完善的解決方案就完成了:)
官方文檔對(duì)這個(gè)問題的說(shuō)明到這里就結(jié)束了,但是可能很多朋友會(huì)有疑問瓢捉,不是說(shuō)不希望Block對(duì)原myController對(duì)象增加強(qiáng)引用么频丘,這里為啥堂而皇之地在Block內(nèi)新定義了一個(gè)強(qiáng)引用,這個(gè)強(qiáng)引用不會(huì)造成循環(huán)引用么泡态?理解這個(gè)問題的關(guān)鍵在于理解被Block捕獲的引用和在Block內(nèi)定義的引用的區(qū)別搂漠。為了搞得明白這個(gè)問題,這里需要了解一些Block的實(shí)現(xiàn)原理某弦,但由于篇幅的緣故桐汤,本文在這里就不展開了而克,詳細(xì)的內(nèi)容可以參考其他的文章,這里特別推薦唐巧的文章和另外2位作者的博文:這個(gè)這個(gè)怔毛,講的都比較清楚员萍。
這里假設(shè)大家已經(jīng)對(duì)Block的實(shí)現(xiàn)原理有所了解了。我們就直入主題了拣度!注意前方高能(=碎绎。=)
為了更清楚地說(shuō)明問題,這里用一個(gè)簡(jiǎn)單的程序舉例抗果。比如我們有如下程序:

#include <stdio.h>

int main()
{
    int b = 10;
    
    int *a = &b;
    
    void (^blockFunc)() = ^(){
    
        int *c = a;

    };
    
    blockFunc();
    
    return 1;
}

程序中筋帖,同為int型的指針,a是被Block捕獲的變量冤馏,而c是在Block內(nèi)定義的變量日麸。我們用clang -rewrite-objc處理后,可以看到如下代碼:
原main函數(shù):

int main()
{
    int b = 10;

    int *a = &b;

    void (*blockFunc)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a);

    ((void (*)(__block_impl *))((__block_impl *)blockFunc)->FuncPtr)((__block_impl *)blockFunc);

    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;
  }
};

實(shí)際執(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默認(rèn)在堆上)睦霎,那么a作為Block結(jié)構(gòu)體的一個(gè)成員梢卸,也自然會(huì)存在于堆上,而c無(wú)論如何副女,永遠(yuǎn)位于Block內(nèi)實(shí)際執(zhí)行代碼的函數(shù)棧內(nèi)蛤高。這也導(dǎo)致了兩個(gè)變量生命周期的完全不同:c在Block的函數(shù)運(yùn)行完畢,即會(huì)被釋放碑幅,而a呢戴陡,只有在Block被從堆上釋放的時(shí)候才會(huì)釋放。
回到我們的MyViewController的例子中沟涨,同上理恤批,如果我們直接讓Block捕獲我們的myController引用,那么這個(gè)引用會(huì)被復(fù)制后(引用類型也會(huì)被復(fù)制)作為Block的成員變量存在于其所在的堆空間中裹赴,也就是為Block增加了一個(gè)指向myController對(duì)象的強(qiáng)引用喜庞,這就是造成循環(huán)引用的本質(zhì)原因。對(duì)于MyViewController的例子棋返,Block的結(jié)構(gòu)體可以理解是這個(gè)樣子:(準(zhǔn)確的結(jié)構(gòu)體肯定和以下這個(gè)有區(qū)別延都,但也肯定是如下這種形式:)

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  MyViewController * __strong myController;  // 被捕獲的強(qiáng)引用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;
  }
};

而反觀我們給Block傳入一個(gè)弱引用weakMyController,這時(shí)我們Block的結(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;
  }
};

再看在Block內(nèi)聲明的強(qiáng)引用strongMyController睛竣,它雖然是強(qiáng)引用晰房,但存在于函數(shù)棧中,在函數(shù)執(zhí)行期間,它一直存在殊者,所以myController對(duì)象也一直存在与境,但是當(dāng)函數(shù)執(zhí)行完畢,strongMyController即被銷毀幽污,于是它對(duì)myController對(duì)象的強(qiáng)引用也被解除嚷辅,這時(shí)Block對(duì)myController對(duì)象就不存在強(qiáng)引用關(guān)系了!加入了strongMyController的函數(shù)大體會(huì)是這個(gè)樣子:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

  MyViewController * __strong strongMyController = __cself->weakMyController; 

    // ....

    }

綜上所述距误,在ARC下(在MRC下會(huì)略有不同)簸搞,Block捕獲的引用和Block內(nèi)聲明的引用無(wú)論是存在空間與生命周期都是截然不同的,也正是這種不同准潭,造成了我們對(duì)他們使用方式的區(qū)別趁俊。
以上就解釋了之前提到的所有問題,希望大家能看明白:)
好的刑然,最后再提一點(diǎn)寺擂,在ARC中,對(duì)Block捕獲對(duì)象的內(nèi)存管理已經(jīng)簡(jiǎn)化了很多泼掠,由于沒有了retain和release等操作怔软,實(shí)際只需要考慮循環(huán)引用的問題就行了。比如下面這種择镇,是沒有內(nèi)存泄露的問題的:

TestObject *aObject = [[TestObject alloc] init];
    
aObject.name = @"hehe";

self.aBlock = ^(){
    
    NSLog(@"aObject's name = %@",aObject.name);
        
};

我們上面提到的解決方案挡逼,只是針對(duì)Block產(chǎn)生循環(huán)引用的問題,而不是說(shuō)所有的Block捕獲引用都要這么處理腻豌,一定要注意家坎!

五、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類型的對(duì)象和Objective-C類型的對(duì)象虱疏。詳細(xì)的內(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時(shí)代苏携,由于Objective-C類型的對(duì)象和Core Foundation類型的對(duì)象都是相同的release和retain操作規(guī)則做瞪,所以Toll-Free Bridging的使用比較簡(jiǎn)單,但是自從ARC加入后右冻,Objective-C類型的對(duì)象內(nèi)存管理規(guī)則改變了穿扳,而Core Foundation依然是之前的機(jī)制,換句話說(shuō)国旷,Core Foundation不支持ARC。
這個(gè)時(shí)候就必須要要考慮一個(gè)問題了茫死,在做Core Foundation與Objective-C類型轉(zhuǎn)換的時(shí)候跪但,用哪一種規(guī)則來(lái)管理對(duì)象的內(nèi)存。顯然,對(duì)于同一個(gè)對(duì)象屡久,我們不能夠同時(shí)用兩種規(guī)則來(lái)管理忆首,所以這里就必須要確定一件事情:哪些對(duì)象用Objective-C(也就是ARC)的規(guī)則,哪些對(duì)象用Core Foundation的規(guī)則(也就是MRC)的規(guī)則被环。或者說(shuō)要確定對(duì)象類型轉(zhuǎn)換了之后糙及,內(nèi)存管理的ownership的改變。

) or a Core Foundation-style macro (defined inNSObject.h
)```
于是蘋果在引入ARC之后對(duì)Toll-Free Bridging的操作也加入了對(duì)應(yīng)的方法與修飾符筛欢,用來(lái)指明用哪種規(guī)則管理內(nèi)存浸锨,或者說(shuō)是內(nèi)存管理權(quán)的歸屬。
這些方法和修飾符分別是:

**__bridge(修飾符)**
只是聲明類型轉(zhuǎn)變版姑,但是不做內(nèi)存管理規(guī)則的轉(zhuǎn)變柱搜。
比如:
```CFStringRef s1 = (__bridge CFStringRef) [[NSString alloc] initWithFormat:@"Hello, %@!", name];```
只是做了NSString到CFStringRef的轉(zhuǎn)化,但管理規(guī)則未變剥险,依然要用Objective-C類型的ARC來(lái)管理s1聪蘸,你不能用CFRelease()去釋放s1。

**__bridge_retained(修飾符) or CFBridgingRetain(函數(shù))**

表示將指針類型轉(zhuǎn)變的同時(shí)表制,將內(nèi)存管理的責(zé)任由原來(lái)的Objective-C交給Core Foundation來(lái)處理健爬,也就是,將ARC轉(zhuǎn)變?yōu)镸RC么介。
比如娜遵,還是上面那個(gè)例子
```NSString *s1 = [[NSString alloc] initWithFormat:@"Hello, %@!", name];CFStringRef s2 = (__bridge_retained CFStringRef)s1;// do something with s2//...CFRelease(s2); // 注意要在使用結(jié)束后加這個(gè)```
我們?cè)诘诙凶隽宿D(zhuǎn)化,這時(shí)內(nèi)存管理規(guī)則由ARC變?yōu)榱薓RC夭拌,我們需要手動(dòng)的來(lái)管理s2的內(nèi)存魔熏,而對(duì)于s1,我們即使將其置為nil鸽扁,也不能釋放內(nèi)存蒜绽。
等同的,我們的程序也可以寫成:
```NSString *s1 = [[NSString alloc] initWithFormat:@"Hello, %@!", name];CFStringRef s2 = (CFStringRef)CFBridgingRetain(s1);// do something with s2//...CFRelease(s2); // 注意要在使用結(jié)束后加這個(gè)```
**__bridge_transfer(修飾符) or CFBridgingRelease(函數(shù))**

這個(gè)修飾符和函數(shù)的功能和上面那個(gè)__bridge_retained相反桶现,它表示將管理的責(zé)任由Core Foundation轉(zhuǎn)交給Objective-C躲雅,即將管理方式由MRC轉(zhuǎn)變?yōu)锳RC。
比如:
```CFStringRef result = CFURLCreateStringByAddingPercentEscapes(. . .);NSString *s = (__bridge_transfer NSString *)result;//or NSString *s = (NSString *)CFBridgingRelease(result);return s;```
這里我們將result的管理責(zé)任交給了ARC來(lái)處理骡和,我們就不需要再顯式地將CFRelease()了相赁。
對(duì)了,這里你可能會(huì)注意到一個(gè)細(xì)節(jié)慰于,和ARC中那個(gè)4個(gè)主要的修飾符(__strong,__weak,...)不同钮科,這里修飾符的位置是放在類型前面的,雖然官方文檔中沒有說(shuō)明婆赠,但看官方的頭文件可以知道绵脯。小伙伴們,記得別把位置寫錯(cuò)哦:)
![](http://upload-images.jianshu.io/upload_images/2790607-55bc514e645550cc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
呼~ 好了,以上就是本篇文章的主要內(nèi)容蛆挫。這次采用了新的排版赃承,感覺比以前有條理得多,希望的大家看的舒服悴侵。
轉(zhuǎn)自:http://www.cnblogs.com/flyFreeZn/p/4264220.html
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瞧剖,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子可免,更是在濱河造成了極大的恐慌抓于,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巴元,死亡現(xiàn)場(chǎng)離奇詭異毡咏,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)逮刨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門呕缭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人修己,你說(shuō)我怎么就攤上這事恢总。” “怎么了睬愤?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵片仿,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我尤辱,道長(zhǎng)砂豌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任光督,我火速辦了婚禮阳距,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘结借。我一直安慰自己筐摘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布船老。 她就那樣靜靜地躺著咖熟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪柳畔。 梳的紋絲不亂的頭發(fā)上馍管,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音薪韩,去河邊找鬼咽斧。 笑死堪置,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的张惹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼岭洲,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼宛逗!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起盾剩,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤雷激,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后告私,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體屎暇,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年驻粟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了根悼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜀撑,死狀恐怖挤巡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情酷麦,我是刑警寧澤矿卑,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站沃饶,受9級(jí)特大地震影響母廷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜糊肤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一琴昆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧轩褐,春花似錦椎咧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至拗踢,卻和暖如春脚牍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背巢墅。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工诸狭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留券膀,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓驯遇,卻偏偏與公主長(zhǎng)得像芹彬,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子叉庐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容