iOS - 揭露Block的內(nèi)部實(shí)現(xiàn)原理

想必大家對(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ò)了?再來(lái)兩個(gè)函數(shù)。這兩個(gè)的結(jié)果與blockFunc2一樣,打印出來(lái)的 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)語(yǔ)言,它的一切都是基于runtime實(shí)現(xiàn)的!在運(yùn)行時(shí)會(huì)將OC轉(zhuǎn)換成C,我們可以利用這個(gè)來(lái)查看關(guān)于block在內(nèi)部是如何實(shí)現(xiàn)的
新建一個(gè)Command Line Tool項(xiàng)目,將以上代碼放入main.m中,如圖

main.m

這里我們打開終端,cd到項(xiàng)目目錄下,然后將用下面的命令將OC重寫為C

clang -rewrite-objc main.m
rewrite-objc

這時(shí)我們可以發(fā)現(xiàn)當(dāng)前目錄下多了一個(gè)main.cpp文件,打開它并滾到最下面


打開main.cpp
main.cpp

這里我們可以看到blockFunc1的C語(yǔ)言實(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)體為


__blockFunc1_block_impl_0

我們來(lái)看下帶__block的blockFunc2

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è)賮?lái)看看 _ 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變量,將其原來(lái)的100修改為200~~

到此,關(guān)于Block內(nèi)部實(shí)現(xiàn)的揭曉也就到此結(jié)束了,希望本文能讓你對(duì)block有更深的理解,感謝你耐心的閱讀!

歡迎關(guān)注微信公眾號(hào):linxunfengtop
最后編輯于
?著作權(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)容

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