想必大家對(duì)block都很熟悉了畅涂,雖然都會(huì)用盈包,但是你真的知道它的原理嗎办龄?比如為什么要加上__block废酷,這個(gè)修飾符到底有什么用?不加會(huì)有什么后果慎式?block又是如何實(shí)現(xiàn)的等等伶氢。。瘪吏。該篇文章就為大家揭曉關(guān)于Block的實(shí)現(xiàn)原理~
拋磚引玉
先給出問題癣防,大家思考下結(jié)果吧,如果分別調(diào)用以下兩個(gè)方法肪虎,結(jié)果如何劣砍?
void blockFunc1()
{
int num = 100;
void (^block)() = ^{
NSLog(@"num equal %d", num);
};
num = 200;
block();
}
void blockFunc2()
{
__block int num = 100;
void (^block)() = ^{
NSLog(@"num equal %d", num);
};
num = 200;
block();
}
答案是
blockFunc1 : num equal 100
blockFunc2 : num equal 200
是不是有人答錯(cuò)了?再來兩個(gè)函數(shù)扇救。這兩個(gè)的結(jié)果與blockFunc2一樣刑枝,打印出來的 num 為 200
// 全局變量
int num = 100;
void blockFunc3()
{
void (^block)() = ^{
NSLog(@"num equal %d", num);
};
num = 200;
block();
}
void blockFunc4()
{
static int num = 100;
void (^block)() = ^{
NSLog(@"num equal %d", num);
};
num = 200;
block();
}
疑問:
我們發(fā)現(xiàn)num做為局部變量時(shí)加上 _ _block 修飾符、num做為全局變量以及num為靜態(tài)局部變量時(shí)在block中輸出結(jié)果是一樣的迅腔,皆為被修改之后的值装畅,而做為局部變量并且未加上__block的num在block中輸出的值卻還是未賦值之前的值。這是為什么呢沧烈?探索這個(gè)問題我們就需要看看底層結(jié)構(gòu)是如何實(shí)現(xiàn)的了
探索內(nèi)部原理
Objective-C是一個(gè)全動(dòng)態(tài)語言掠兄,它的一切都是基于runtime實(shí)現(xiàn)的!在運(yùn)行時(shí)會(huì)將OC轉(zhuǎn)換成C,我們可以利用這個(gè)來查看關(guān)于block在內(nèi)部是如何實(shí)現(xiàn)的
新建一個(gè)Command Line Tool項(xiàng)目蚂夕,將以上代碼放入main.m中迅诬,如圖
這里我們打開終端,cd到項(xiàng)目目錄下婿牍,然后將用下面的命令將OC重寫為C
clang -rewrite-objc main.m
這時(shí)我們可以發(fā)現(xiàn)當(dāng)前目錄下多了一個(gè)main.cpp文件侈贷,打開它并滾到最下面
這里我們可以看到blockFunc1的C語言實(shí)現(xiàn)方法
void blockFunc1()
{
int num = 100;
void (*block)() = ((void (*)())&__blockFunc1_block_impl_0((void *)__blockFunc1_block_func_0, &__blockFunc1_block_desc_0_DATA, num));
num = 200;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
去掉類型轉(zhuǎn)換
void blockFunc1()
{
int num = 100;
// *************************重點(diǎn)句***********************
void (*block)() = &__blockFunc1_block_impl_0(__blockFunc1_block_func_0, &__blockFunc1_block_desc_0_DATA, num));
// *****************************************************
num = 200;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
這里我們可以看到
block實(shí)際上是指向結(jié)構(gòu)體的指針
該結(jié)構(gòu)體為
我們來看下帶__block的blockFunc2
在 blockFunc1 中,block指向了一個(gè)名為__blockFunc1_block_impl_0的結(jié)構(gòu)體等脂,并且在初始化時(shí)輸入了三個(gè)參數(shù)(__blockFunc1_block_impl_0最后的flags有默認(rèn)參數(shù)俏蛮,所以可以不用傳參),第三個(gè)參數(shù)就是我們寫的num上遥,與blockFunc2相比較搏屑,這里的num并沒有帶*號(hào),所以說在這里它只是傳值而非傳址粉楚,而下面的【num = 200;】也就沒什么卵用了辣恋。這就是blockFunc2、blockFunc3與blockFunc4為什么能打印出num改變后的值解幼,而blockFunc1不行的原因抑党。
在這里我們也可以看出:
編譯器會(huì)將block的內(nèi)部代碼生成對(duì)應(yīng)的函數(shù)
** SO **
我們總結(jié)下包警,block在內(nèi)部會(huì)作為一個(gè)指向結(jié)構(gòu)體的指針撵摆,當(dāng)調(diào)用block的時(shí)候其實(shí)就是根據(jù)block對(duì)應(yīng)的指針找到相應(yīng)的函數(shù),進(jìn)而進(jìn)行調(diào)用害晦,并傳入自身
__block的實(shí)現(xiàn)
我們?cè)賮砜纯?_ block特铝, _block也被轉(zhuǎn)換成了結(jié)構(gòu)體,并含有5個(gè)變量
struct __Block_byref_num_0 {
void *__isa; // isa指針
__Block_byref_num_0 *__forwarding; // 實(shí)例本身
int __flags;
int __size;
int num; // 我們的num值
};
圖片對(duì)應(yīng)著blockFunc2中的
__block int num = 100;
當(dāng)創(chuàng)建num并用__block修飾的時(shí)候壹瘟,會(huì)初始化這五個(gè)變量
當(dāng)我們執(zhí)行
num = 200;
對(duì)應(yīng)著
(num.__forwarding->num) = 200;
上面剛剛提到過 _ _forwarding是實(shí)例本身鲫剿,即類型結(jié)構(gòu)體__Block_byref_num_0的&num,再找到對(duì)應(yīng)的num變量稻轨,將其原來的100修改為200~~
到此灵莲,關(guān)于Block內(nèi)部實(shí)現(xiàn)的揭曉也就到此結(jié)束了,希望本文能讓你對(duì)block有更深的理解殴俱,感謝你耐心的閱讀政冻!