iOS - 深入探究Block循環(huán)引用

作者:Mitchell 

一、根據(jù)需求提出問題

  • 請(qǐng)耐心把這篇文章看完,你對(duì) Block 會(huì)有更深刻的了解。
  • 這里直接用一個(gè)需求來探究循環(huán)引用的問題:如果我想在Block中延時(shí)來運(yùn)行某段代碼,這里就會(huì)出現(xiàn)一個(gè)問題,看這段代碼:
 - (void)viewDidLoad {
    [super viewDidLoad];
    MitPerson*person = [[MitPerson alloc]init];
    __weak MitPerson * weakPerson = person;
    person.mitBlock = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [weakPerson test];
        });
    };
    person.mitBlock();
}

直接運(yùn)行這段代碼會(huì)發(fā)現(xiàn)[weakPerson test];并沒有執(zhí)行,打印一下會(huì)發(fā)現(xiàn),weakPerson 已經(jīng)是 Nil 了,這是由于當(dāng)我們的 viewDidLoad 方法運(yùn)行結(jié)束,由于是局部變量,無論是 MitPerson 和 weakPerson 都會(huì)被釋放掉,那么這個(gè)時(shí)候在 Block 中就無法拿到正真的 person 內(nèi)容了。

  • 按如下方法修改代碼:
 - (void)viewDidLoad {
    [super viewDidLoad];
    MitPerson*person = [[MitPerson alloc]init];
    __weak MitPerson * weakPerson = person;
    person.mitBlock = ^{
        __strong MitPerson * strongPerson = weakPerson;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [strongPerson test];
        });
    };
    person.mitBlock();
}

這樣當(dāng)2秒過后,計(jì)時(shí)器依然能夠拿到想要的 person 對(duì)象。

二、深入探究原理

  • 這里將會(huì)對(duì)每行代碼逐步進(jìn)行說明
1、開辟一段控件存儲(chǔ) person 類對(duì)象內(nèi)容,創(chuàng)建 person 強(qiáng)指針。
 MitPerson*person = [[MitPerson alloc]init];
2、創(chuàng)建一個(gè)弱指針 weakPerson 指向person對(duì)象內(nèi)容 
 __weak MitPerson * weakPerson = person;
  person.mitBlock = ^{
  3、在 person 對(duì)象的 Block 內(nèi)部創(chuàng)建一個(gè)強(qiáng)指針來指向 person 對(duì)象,為了保證當(dāng)計(jì)時(shí)器執(zhí)行代碼的時(shí)候,person 對(duì)象沒有被系統(tǒng)銷毀所以我們必須在系統(tǒng)內(nèi)部進(jìn)行一次強(qiáng)引用,并用 GCD 計(jì)時(shí)器引用 strongPerson,為了保留 person 對(duì)象,在下面會(huì)對(duì)這里更加詳細(xì)的說明。
    __strong MitPerson * strongPerson = weakPerson;
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
          [strongPerson test];
      });
  };
4、執(zhí)行 Block 代碼
    person.mitBlock();
  • 下面將詳細(xì)分析一下下面這段代碼:
  person.mitBlock = ^{
 __strong MitPerson * strongPerson = weakPerson;
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
          [strongPerson test];
      });
  };
  • 首先需要明白一些關(guān)于 Block 的概念:
    • 1、默認(rèn)情況下,block 是放在棧里面的
    • 2、一旦blcok進(jìn)行了copy操作,block的內(nèi)存就會(huì)被放在堆里面
    • 3、堆立面的block(被copy過的block)有以下現(xiàn)象
      • 1> block內(nèi)部如果通過外面聲明的強(qiáng)引用來使用,那么block內(nèi)部會(huì)自動(dòng)產(chǎn)生一個(gè)強(qiáng)引用指向所使用的對(duì)象。
      • 2> block內(nèi)部如果通過外面聲明的弱引用來使用,那么block內(nèi)部會(huì)自動(dòng)產(chǎn)生一個(gè)弱引用指向所使用的對(duì)象。
  • 我們進(jìn)行這段代碼的目的:
    • 首先,我們需要在 Block 塊中調(diào)用,person 對(duì)象的方法,既然是在 Block 塊中我們就應(yīng)該使用弱指針來引用外部變量,以此來避免循環(huán)引用。但是又會(huì)出現(xiàn)問題,什么問題呢?就是當(dāng)我計(jì)時(shí)器要執(zhí)行方法的時(shí)候,發(fā)現(xiàn)對(duì)象已經(jīng)被釋放了。
    • 接下來就是為了避免 person 對(duì)象在計(jì)時(shí)器執(zhí)行的時(shí)候被釋放掉:那么為什么 person 對(duì)象會(huì)被釋放掉呢?因?yàn)闊o論我們的person強(qiáng)指針還是 weakPerson 弱指針都是局部變量,當(dāng)執(zhí)行完ViewDidLoad 的時(shí)候,指針會(huì)被銷毀。對(duì)象只有被強(qiáng)指針引用的時(shí)候才不會(huì)被銷毀,而我們?nèi)绻苯右猛獠康膹?qiáng)指針對(duì)象又會(huì)產(chǎn)生循環(huán)引用,這個(gè)時(shí)候我們就用了一個(gè)巧妙的代碼來完成這個(gè)需求。
    • 首先在 person.mitBlock 引用外部 weakPerson,并在內(nèi)部創(chuàng)建一個(gè)強(qiáng)指針去指向 person 對(duì)象,因?yàn)樵趦?nèi)部聲明變量,Block 是不會(huì)強(qiáng)引用這個(gè)對(duì)象的,這也就在避免的 person.mitBlock 循環(huán)引用風(fēng)險(xiǎn)的同時(shí),又創(chuàng)建出了一個(gè)強(qiáng)指針指向?qū)ο蟆?/li>
    • 之后再用 GCD 延時(shí)器 Block 來引用相對(duì)于它來說是外部的變量 strongPerson ,這時(shí)延時(shí)器 Block 會(huì)默認(rèn)創(chuàng)建出來一個(gè)強(qiáng)引用來引用 person 對(duì)象,當(dāng) person.mitBlock 作用域結(jié)束之后 strongPerson 會(huì)跟著被銷毀,內(nèi)存中就僅剩下了 延時(shí)器 Block 強(qiáng)引用著 person 對(duì)象,2秒之后觸發(fā) test 方法,GCD Block 內(nèi)部方法執(zhí)行完畢之后,延時(shí)器和對(duì)象都被銷毀,這樣就完美實(shí)現(xiàn)了我們的需求。
  • 最后再用一張圖來闡述各個(gè)指針、Block 與對(duì)象之間的關(guān)系
    黑色代表強(qiáng)引用,綠色代表弱引用
    • 總結(jié):person.mitBlock 中創(chuàng)建 strongPerson 是為了能夠使 GCD Block 保存 person 對(duì)象,創(chuàng)建 strongPerson 時(shí)候使用 weakPerson 是為了避免 mitBlock 直接引用外部強(qiáng)指針變量所造成的循環(huán)引用。
      Block循環(huán)引用.png

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

相關(guān)閱讀更多精彩內(nèi)容

  • 《Objective-C高級(jí)編程》這本書就講了三個(gè)東西:自動(dòng)引用計(jì)數(shù)、block、GCD,偏向于從原理上對(duì)這些內(nèi)容...
    WeiHing閱讀 10,085評(píng)論 10 69
  • iOS代碼塊Block 概述 代碼塊Block是蘋果在iOS4開始引入的對(duì)C語言的擴(kuò)展,用來實(shí)現(xiàn)匿名函數(shù)的特性,B...
    smile刺客閱讀 2,466評(píng)論 2 26
  • Block使用場(chǎng)景,可以在兩個(gè)界面的傳值,也可以對(duì)代碼封裝作為參數(shù)的傳遞等。用過GCD就知道Block的精妙之處。...
    Coder_JMicheal閱讀 814評(píng)論 2 1
  • 請(qǐng)耐心把這篇文章看完,你對(duì) Block 會(huì)有更深刻的了解。 這里直接用一個(gè)需求來探究循環(huán)引用的問題:如果我想在Bl...
    平凡的心閱讀 779評(píng)論 1 10
  • 前言 Blocks是C語言的擴(kuò)充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,851評(píng)論 0 23

友情鏈接更多精彩內(nèi)容