Block如何捕獲外部變量二:對(duì)象類型

上一篇我們講到了Block是如何捕獲基本數(shù)據(jù)類型的,今天我們研究一下block是如何捕獲對(duì)象類型的數(shù)據(jù).
我們用一個(gè)小問(wèn)題開(kāi)始本篇的主題:在ARC環(huán)境下,我們先創(chuàng)建一個(gè)Person類,再重寫Person類的dealloc方法,然后看下面代碼

當(dāng)我們的代碼走到斷點(diǎn)的時(shí)候,Person被銷毀了,這很容易理解,因?yàn)槌隽?code>person變量的作用域,我們對(duì)這段代碼稍加修改,如下:


我們?cè)?code>block中使用了person,會(huì)發(fā)現(xiàn)代碼再次走到斷點(diǎn)的時(shí)候,person并沒(méi)有釋放,這是為什么呢?我們看一下轉(zhuǎn)換后的底層代碼:

補(bǔ)充于2019-12-22 22:55分.感謝萬(wàn)能的喜劇提出的問(wèn)題,之前寫的的確不夠詳細(xì),針對(duì)此問(wèn)題再做補(bǔ)充:
其實(shí)上述block的類型并不是我們所看到的_NSConcreteStackBlock類型,雖然轉(zhuǎn)換為底層代碼后顯示的是_NSConcreteStackBlock類型.但是我們?cè)趯?code>block進(jìn)行賦值操作后,runtime會(huì)自動(dòng)對(duì)這個(gè)block進(jìn)行copy操作,所以此時(shí)的block類型其實(shí)是NSMallocBlock類型:

NSMallocBlock

所以要在ARC環(huán)境下查看_NSConcreteStackBlock類型的block就不要對(duì)其進(jìn)行賦值操作:
StackBlock 不會(huì)引用外部變量

我們先假設(shè)在block內(nèi)部對(duì)Person的捕獲是強(qiáng)引用的,因?yàn)橹挥惺菑?qiáng)引用才能解釋為什么person出了作用域后仍然沒(méi)有銷毀,接下來(lái)我們來(lái)驗(yàn)證一下.
我們將 ARC 改成 MRC ,然后運(yùn)行:


會(huì)發(fā)現(xiàn)同樣的代碼,Person釋放了.因?yàn)檫@是在 MRC 環(huán)境下,我們知道訪問(wèn)了 auto 變量block__NSStackBlock類型,由此我們可以得出結(jié)論:棧內(nèi)存中的block是不會(huì)強(qiáng)引用外部變量的
為了驗(yàn)證這個(gè)結(jié)論的正確性,同樣在 MRC 環(huán)境下,我們把剛才棧上的block進(jìn)行copy操作:

很明顯,Person沒(méi)有釋放,說(shuō)明了在 MRC 環(huán)境下,堆空間上的block會(huì)對(duì)Person進(jìn)行一次相當(dāng)于retain操作,保住了Person的命.
結(jié)論:不管是 ARC 環(huán)境還是 MRC 環(huán)境,椘空間的block是不會(huì)擁有外部對(duì)象的;堆空間的block會(huì)擁有外部對(duì)象,在 ARC 環(huán)境下就是強(qiáng)引用,在 MRC 環(huán)境下就是 retain.
我們?cè)賹?duì)上面的代碼稍加修改:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MYBlock block;
        {
            Person *person = [[Person alloc]init];
            person.age = 10;
            __weak Person *weakPerson = person;
            block = ^{
                NSLog(@"--------%d",weakPerson.age);
            };
        }
        //會(huì)發(fā)現(xiàn)走到這里的時(shí)候, Person 已經(jīng)釋放了
        NSLog(@"--------");
    }
    return 0;
}

我們使用__weak修飾person,會(huì)發(fā)現(xiàn)Person對(duì)象走到NSLog處時(shí)會(huì)釋放掉,相信有很多人都使用過(guò)__weak關(guān)鍵字修飾block內(nèi)部訪問(wèn)的局部變量來(lái)防止循環(huán)引用,但這是為什么呢?我們繼續(xù)通過(guò)clang編譯器轉(zhuǎn)換成 C++ 代碼,查看一下底層,我們繼續(xù)使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc命令來(lái)轉(zhuǎn)換OC文件,會(huì)發(fā)現(xiàn)報(bào)錯(cuò),如圖:


這報(bào)錯(cuò)的大概意思是,不能創(chuàng)建弱引用!這是因?yàn)槿跻玫募夹g(shù)是需要runtime動(dòng)態(tài)支持的,靜態(tài)的編譯代碼是沒(méi)辦法轉(zhuǎn)換的.解決辦法就是告訴編譯器當(dāng)前環(huán)境是 ARC 環(huán)境并且指定運(yùn)行時(shí)的版本即可.完整的命令行如下:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
成功轉(zhuǎn)換后的底層代碼如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__weak weakPerson;//此處變成了 弱引用
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

我們?cè)侔?code>__weak去掉,再轉(zhuǎn)換做一下對(duì)比:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MYBlock block;
        {
            Person *person = [[Person alloc]init];
            person.age = 10;
//            __weak Person *weakPerson = person;
            block = ^{
                //去掉弱引用,使用 person
                NSLog(@"--------%d",person.age);
            };
        }
        NSLog(@"--------");
    }
    return 0;
}

對(duì)比一下底層:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__strong person; //這里也變成了 __strong
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

通過(guò)對(duì)比我們可以得出結(jié)論:block內(nèi)部在訪問(wèn)外部的auto變量時(shí),外部auto變量是以什么指針訪問(wèn)的(strong / weak),結(jié)構(gòu)體內(nèi)內(nèi)部也是以什么指針訪問(wèn).
我們?cè)谵D(zhuǎn)換后的.cpp文件中找到__main_block_desc_0這個(gè)結(jié)構(gòu)體:(我們?cè)?a href="http://www.reibang.com/p/e6759404f9cd" target="_blank">block的本質(zhì)中已經(jīng)對(duì)block內(nèi)部的結(jié)構(gòu)體有詳細(xì)的解析,不懂的同學(xué)可以查看)

__main_block_desc_0結(jié)構(gòu)體

我們知道之前我們了解的__main_block_desc_0結(jié)構(gòu)體中只有reservedBlock_size兩個(gè)成員,現(xiàn)在又增加了copy,dispose兩個(gè)函數(shù)指針,這兩個(gè)函數(shù)指針有什么作用呢?注意:只有在訪問(wèn)對(duì)象類型的auto變量時(shí)才會(huì)生成copy dispose.通過(guò)底層代碼,我們可以看出,copy指針指向__main_block_copy_0函數(shù),dispose指針指向__main_block_dispose_0函數(shù).

  • __main_block_copy_0
    當(dāng)椙蟛蓿空間的block在進(jìn)行copy操作變成堆空間的block時(shí),block內(nèi)部會(huì)自動(dòng)調(diào)用__main_block_copy_0函數(shù),我們看看block函數(shù)的內(nèi)部:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)

{_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
    
}

__main_block_copy_0內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù),并且把person當(dāng)做參數(shù)傳遞進(jìn)去.而在_Block_object_assign函數(shù)內(nèi)部,會(huì)根據(jù)auto變量的修飾符(__strong,__weak,__unretained)進(jìn)行相應(yīng)的操作,形成強(qiáng)引用(retain)或者弱引用

  • __main_block_dispose_0
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

如果block從堆中移除(block銷毀)時(shí),會(huì)調(diào)用block內(nèi)部的dispose函數(shù),dispose函數(shù)內(nèi)部又會(huì)調(diào)用__main_block_dispose_0函數(shù),__main_block_dispose_0函數(shù)會(huì)自動(dòng)釋放引用的auto變量(release)

結(jié)論:
當(dāng)block內(nèi)部訪問(wèn)了對(duì)象類型的auto變量時(shí):
1:如果block是在棧上,將不會(huì)對(duì)auto變量產(chǎn)生強(qiáng)引用(不管是 MRC 或者 ARC)
2:如果block是在堆上,就說(shuō)明block進(jìn)行過(guò)copy操作,進(jìn)行copy操作的block會(huì)自動(dòng)調(diào)用block內(nèi)部的__main_block_copy_0函數(shù),__main_block_copy_0函數(shù)內(nèi)部會(huì)根據(jù)auto變量的修飾符形成相應(yīng)的強(qiáng)引用(retain)或者弱引用.
3:當(dāng)block銷毀時(shí),block會(huì)自動(dòng)調(diào)用內(nèi)部的dispose函數(shù),dispose函數(shù)會(huì)自動(dòng)調(diào)用內(nèi)部的__main_block_dispose_0釋放引用的auto變量.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市读规,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌揪胃,老刑警劉巖涉馅,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異停团,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)掏熬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門佑稠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人旗芬,你說(shuō)我怎么就攤上這事舌胶。” “怎么了疮丛?”我有些...
    開(kāi)封第一講書人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵辆琅,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我这刷,道長(zhǎng)婉烟,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任暇屋,我火速辦了婚禮似袁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己昙衅,他們只是感情好扬霜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著而涉,像睡著了一般著瓶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上啼县,一...
    開(kāi)封第一講書人閱讀 51,610評(píng)論 1 305
  • 那天材原,我揣著相機(jī)與錄音,去河邊找鬼季眷。 笑死余蟹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的子刮。 我是一名探鬼主播威酒,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼挺峡!你這毒婦竟也來(lái)了葵孤?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤橱赠,失蹤者是張志新(化名)和其女友劉穎尤仍,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體病线,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年鲤嫡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了送挑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡暖眼,死狀恐怖惕耕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情诫肠,我是刑警寧澤司澎,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站栋豫,受9級(jí)特大地震影響挤安,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丧鸯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一蛤铜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦围肥、人聲如沸剿干。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)置尔。三九已至,卻和暖如春氢伟,著一層夾襖步出監(jiān)牢的瞬間榜轿,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工腐芍, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留差导,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓猪勇,卻偏偏與公主長(zhǎng)得像设褐,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子泣刹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

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