前言
在Objective-C之Blocks(一)中,說明了Block的一些用法和特性。其中講到Block的三種特性:
- 截獲自動變量
- __block說明符
- 截獲的自動變量
但是我們還是不知道Block的實質和這些特性是如何實現的。本文將介紹Block的本質。
Block本質
想要了解Block的本質,我們就需要利用clang(LLVM編譯器)將代碼轉換為C++代碼閱讀。我們先在終端中進入main.m文件路徑,在終端中輸入
clang -rewrite-objc main.m(文件名字)
轉換后的文件變得很長,是因為編譯器對頭文件的處理,我們可以忽略不看,直接在文件里面搜索int main
,我們可以看到轉換過后的main函數,和Block的實現。
分析main函數,我們可以看到一個Block類型變量,其值是一個__main_block_impl_0
結構體,在調用該結構體構造函數的時候,傳入了兩個參數__main_block_func_0
函數指針和__main_block_desc_0_DATA
結構體。
__main_block_func_0函數
觀察__main_block_func_0
函數
在函數中參數:__cself
和OC中的self相同。
觀察函數我們發現,其中有NSLog
。由此我們可以推測出,__main_block_func_0
函數是我們自己定義的函數主體。我們將Block中的代碼塊更改成for循環來驗證我們的猜想。
轉換后的__main_block_func_0
函數
猜想正確,所以,__main_block_func_0
函數代表了Block中,我們自己定義的代碼塊。Block通過將此函數指針傳遞給__main_block_impl_0
結構體指針來實現調用代碼塊。
__main_block_desc_0_DATA結構體
觀察__main_block_desc_0_DATA
結構體
其中有2個成員變量,一個是reserved
,它代表今后版本升級所需要的區域;還有一個是Block_size
,它代表Block大小。而Block的大小是__main_block_impl_0
結構體的大小。
__main_block_impl_0結構體
觀察__main_block_impl_0
結構體
該結構體中寫入了其構造函數,所以看起來比較復雜。去掉構造函數后,其聲明如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
};
第一個成員變量是__block_impl
結構體;第二個成員變量是__main_block_desc_0
結構體指針,此結構體前文已經說明過,不再贅述。
關于__main_block_impl_0
結構體的構造函數,傳入的三個參數:fp
、desc
、flags
分別代表函數指針(此函數指針即為我們自已定義的代碼塊)
、__main_block_desc_0結構體指針
、一個標志位(一般為0)
__block_impl結構體
下面我們著重來看__block_impl
結構體,在cpp文件中搜索__block_impl
此結構體中有4個成員變量:
- isa指針 : 指向一個類對象
- Flags : 某種標志
- Reserved : 今后版本升級所需的區域
- FuncPtr : 函數指針,指向我們自己的代碼塊
了解了__block_impl結構體,所以我們可以將__main_block_impl_0結構體寫成如下形式:
struct __main_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0* Desc;
};
該結構體根據構造函數會像下面這樣初始化:
isa = &_NSConcreateStackBlck;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
其中_NSConcreateStackBlck代表一個對象,具體的在下一部分中講解。
Block的使用
Block的使用為:
blk();
可轉換為以下形式:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
去掉轉換部分:
(*blk->impl.FuncPtr)(blk);
這是簡單的使用函數指針調用函數,傳遞的參數為block本身,也證明的前文所述__cself
和OC中的self相同。
總結
- 在我們創建Block的時候,會生成
__main_block_impl_0
結構體變量賦值給Block變量。由于該結構體中存在isa指針,所以使block成為了OC對象,即該結構體相當于基于objc_object結構體的OC類對象結構體。(關于isa指針請參見:關于oc運行時 isa指針詳解)我們以__main_block_func_0
函數指針(其指向我們自定義的代碼塊所在函數)和__main_block_desc_0_DATA
結構體(其保存了今后升級所需區域和Block大小)來初始化__main_block_impl_0
結構體。通過過函數指針的調用,我們就實現了Block的使用。
* 本文重點講述Block的本質,關于Block的特性,請參見以后的文章。 - 本文重點講述Block的本質,關于Block的特性,請參見Objective-C之Blocks(三)