本文會(huì)記錄下最近對(duì)B lock 的一些探究,先從 block 是如何對(duì)局部變量捕獲開始講起.
上邊的代碼 auto變量的值改變時(shí),block 內(nèi)部輸出結(jié)果還是10,而 static 的局部變量 值變成了50. 那么 block 內(nèi)部是如何對(duì)age height 進(jìn)行處理的呢?
可以通過 xcrun -sdk phones clang -arch arm64 -rewrite-objc main.c -o main.cpp 將OC代碼轉(zhuǎn)成C++代碼實(shí)現(xiàn)
void testBlock() {
auto int age = 10;
static int height = 100;
void(*block)(void) = &__testBlock_block_impl_0(
&__testBlock_block_func_0,
&__testBlock_block_desc_0_DATA, age, &height));
age = 20;
height = 50;
block->FuncPtr(block);
__testBlock_block_func_0 block實(shí)現(xiàn)部分的代碼
__testBlock_block_desc_0_DATA block 內(nèi)存占用大小 描述信息
__testBlock_block_impl_0 構(gòu)造方法 返回一個(gè) __testBlock_block_impl_0 結(jié)構(gòu)體對(duì)象 并且取地址 復(fù)制給 *block 變量
block->FuncPtr 實(shí)質(zhì)就是 block->impl.FuncPtr 由于結(jié)構(gòu)體內(nèi)部第一個(gè)成員就是該結(jié)構(gòu)體的內(nèi)存地址 ,所以 __testBlock_block_impl_0 可以看成 __block_impl 因?yàn)樗闹羔樀刂肪褪侵赶虻谝粋€(gè)成員 __block_impl的地址 ,所以通過強(qiáng)制轉(zhuǎn)換后 可以 block->FuncPtr(block); 調(diào)用 block
}
上邊的代碼還可以簡(jiǎn)化為
void testBlock() {
auto int age = 10;
static int height = 100;
void(*block)(void) = &__testBlock_block_impl_0(__testBlock_block_func_0, &__testBlock_block_desc_0_DATA, age, &height));
age = 20;
height = 50;
block->FuncPtr(block); 調(diào)用 block對(duì)應(yīng)的函數(shù)指針
__testBlock_block_func_0 ///block 代碼的實(shí)現(xiàn) 相當(dāng)于一個(gè)函數(shù)變量
}
我們寫的block 最終被轉(zhuǎn)成一個(gè) struct __testBlock_block_impl_0 結(jié)構(gòu)體 ,并將 block 快代碼方法實(shí)現(xiàn) 封裝到一個(gè) __block_impl 結(jié)構(gòu)體里的 void *FuncPtr;
以下對(duì) block 的數(shù)據(jù)類型進(jìn)行詳細(xì)的說明
struct __testBlock_block_impl_0 {
struct __block_impl impl; ///block函數(shù)回調(diào) 和 調(diào)用環(huán)境
struct __testBlock_block_desc_0* Desc; ///描述了 block 占用多少內(nèi)存
int age; ///對(duì)局部變量age的捕獲
int *height; ///對(duì)局部變量height的捕獲
///結(jié)構(gòu)體構(gòu)造方法 會(huì)對(duì)結(jié)構(gòu)體內(nèi)部所有成員進(jìn)行賦值 相當(dāng)于 init方法
__testBlock_block_impl_0(void *fp, struct __testBlock_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock; ///block 內(nèi)部 isa 指針 block 實(shí)質(zhì)就是一個(gè)OC對(duì)象 封裝了函數(shù)調(diào)用和函數(shù)調(diào)用的環(huán)境
impl.Flags = flags;
impl.FuncPtr = fp; /// 函數(shù)指針 我們 block 里邊的實(shí)現(xiàn)代碼 都保存到這個(gè)函數(shù)地址里 ,將來(lái)執(zhí)行 block 代碼塊 就是執(zhí)行函數(shù)指針指向的函數(shù)實(shí)現(xiàn)
Desc = desc; ///對(duì) block 大小的描述
}
};
auto變量 隨用隨開用完即銷 因此 block 捕獲 auto 變量時(shí) 只把值傳遞進(jìn)去進(jìn)來(lái)了保存 ,當(dāng) auto 變量銷毀的時(shí)候 也不影響 block 內(nèi)部對(duì)這個(gè) auto 變量的使用.
static局部變量 并不隨著函數(shù)作用域結(jié)束而銷毀 它會(huì)一直存在內(nèi)存中直到程序聲明周期結(jié)束才釋放掉,因此 block 對(duì)局部靜態(tài)變量的捕獲是以指針地址進(jìn)行傳遞,由于是指針賦值而不是值傳遞 所以 height 改變時(shí) block 內(nèi)部輸出的值時(shí)最新的 height 的值.
這個(gè)函數(shù)封裝了 block代碼的實(shí)現(xiàn) 即 ^{
NSLog(@"age = %d height = %d",age,height);
};
由FuncPtr 函數(shù)指針調(diào)用該函數(shù)
static void __testBlock_block_func_0(struct __testBlock_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
int *height = __cself->height; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hj_pwgsq9614nb0vq4zd315tcx80000gn_T_main_a95244_mi_0,age,(*height));
}
還可以將 block 強(qiáng)制轉(zhuǎn)換成 __block_impl 結(jié)構(gòu)體 進(jìn)行函數(shù)的調(diào)用
輸出結(jié)果跟 block() 是一樣的
由于 block 內(nèi)部會(huì)對(duì)局部變量進(jìn)行捕獲 所以就會(huì)造成循環(huán)引用的問題 ,比如 self 持有 block ,而 block 內(nèi)部會(huì)使用到 self ,這種情況下就會(huì)造成 block 和OC對(duì)象內(nèi)存泄露問題
OC的方法能夠使用到 self _cmd 是因?yàn)橛袃蓚€(gè) 匿名參數(shù) id self , SEL _cmd ,所以 self 在方法里邊也是一個(gè)局部的變量 這個(gè)時(shí)候 block 就會(huì)捕獲這個(gè) self ,而 self 又持有 block 因此造成了循環(huán)引用 我們通過C++代碼一探究竟
可以看出來(lái) block 內(nèi)部有一個(gè)Person *self 的指針 , 在給 block 賦值的時(shí)候 將 self 的內(nèi)存地址傳遞進(jìn)去 ,這樣就造成了 互相持有,所以 block 使用過程中需要注意循環(huán)引用的問題