想必大家對block都很熟悉了,雖然都會用,但是你真的知道它的原理嗎?比如為什麼要加上__block,這個修飾符到底有什麼用?不加會有什麼後果?block又是如何實現的等等。。。該篇文章就為大家揭曉關於Block的實現原理~
拋磚引玉
先給出問題,大家思考下結果吧,如果分別呼叫以下兩個方法,結果如何?
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
複製程式碼
是不是有人答錯了?再來兩個函式。這兩個的結果與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();
}
複製程式碼
疑問: 我們發現num做為區域性變數時加上 _ _block 修飾符、num做為全域性變數以及num為靜態區域性變數時在block中輸出結果是一樣的,皆為被修改之後的值,而做為區域性變數並且未加上__block的num在block中輸出的值卻還是未賦值之前的值。這是為什麼呢?探索這個問題我們就需要看看底層結構是如何實現的了
探索內部原理
Objective-C是一個全動態語言,它的一切都是基於runtime實現的!在執行時會將OC轉換成C,我們可以利用這個來檢視關於block在內部是如何實現的 新建一個Command Line Tool專案,將以上程式碼放入main.m中,如圖
這裡我們開啟終端,cd到專案目錄下,然後將用下面的命令將OC重寫為C
clang -rewrite-objc main.m
複製程式碼
這時我們可以發現當前目錄下多了一個main.cpp檔案,開啟它並滾到最下面
這裡我們可以看到blockFunc1的C語言實現方法
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);
}
複製程式碼
去掉型別轉換
void blockFunc1()
{
int num = 100;
// *************************重點句***********************
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實際上是指向結構體的指標
該結構體為
我們來看下帶__block的blockFunc2
在 blockFunc1 中,block指向了一個名為__blockFunc1_block_impl_0的結構體,並且在初始化時輸入了三個引數(__blockFunc1_block_impl_0最後的flags有預設引數,所以可以不用傳參),第三個引數就是我們寫的num,與blockFunc2相比較,這裡的num並沒有帶*號,所以說在這裡它只是傳值而非傳址,而下面的【num = 200;】也就沒什麼卵用了。這就是blockFunc2、blockFunc3與blockFunc4為什麼能列印出num改變後的值,而blockFunc1不行的原因。在這裡我們也可以看出:
編譯器會將block的內部程式碼生成對應的函式
** SO **
我們總結下,block在內部會作為一個指向結構體的指標,當呼叫block的時候其實就是根據block對應的指標找到相應的函式,進而進行呼叫,並傳入自身
__block的實現
我們再來看看 _ block, _block也被轉換成了結構體,並含有5個變數
struct __Block_byref_num_0 {
void *__isa; // isa指標
__Block_byref_num_0 *__forwarding; // 例項本身
int __flags;
int __size;
int num; // 我們的num值
};
複製程式碼
圖片對應著blockFunc2中的
__block int num = 100;
複製程式碼
當建立num並用__block修飾的時候,會初始化這五個變數 當我們執行
num = 200;
複製程式碼
對應著
(num.__forwarding->num) = 200;
複製程式碼
上面剛剛提到過 _ _forwarding是例項本身,即型別結構體__Block_byref_num_0的&num,再找到對應的num變數,將其原來的100修改為200~~
到此,關於Block內部實現的揭曉也就到此結束了,希望本文能讓你對block有更深的理解,感謝你耐心的閱讀!