上一篇我們講到了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)體中只有reserved
和Block_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
變量.