Objective-C之Blocks(二)

前言

Objective-C之Blocks(一)中,說明了Block的一些用法和特性。其中講到Block的三種特性:

  • 截獲自動變量
  • __block說明符
  • 截獲的自動變量

但是我們還是不知道Block的實質和這些特性是如何實現的。本文將介紹Block的本質。

Block本質

想要了解Block的本質,我們就需要利用clang(LLVM編譯器)將代碼轉換為C++代碼閱讀。我們先在終端中進入main.m文件路徑,在終端中輸入

clang -rewrite-objc main.m(文件名字)

轉換后的文件變得很長,是因為編譯器對頭文件的處理,我們可以忽略不看,直接在文件里面搜索int main,我們可以看到轉換過后的main函數,和Block的實現。

int main函數

分析main函數,我們可以看到一個Block類型變量,其值是一個__main_block_impl_0結構體,在調用該結構體構造函數的時候,傳入了兩個參數__main_block_func_0函數指針和__main_block_desc_0_DATA結構體。

__main_block_func_0函數

觀察__main_block_func_0函數

__main_block_func_0函數

在函數中參數:__cself和OC中的self相同。
觀察函數我們發現,其中有NSLog。由此我們可以推測出,__main_block_func_0函數是我們自己定義的函數主體。我們將Block中的代碼塊更改成for循環來驗證我們的猜想。

main函數

轉換后的__main_block_func_0函數

轉換后的`__main_block_func_0`函數

猜想正確,所以,__main_block_func_0函數代表了Block中,我們自己定義的代碼塊。Block通過將此函數指針傳遞給__main_block_impl_0結構體指針來實現調用代碼塊。

__main_block_desc_0_DATA結構體

觀察__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結構體

__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結構體的構造函數,傳入的三個參數:fpdescflags分別代表函數指針(此函數指針即為我們自已定義的代碼塊)__main_block_desc_0結構體指針一個標志位(一般為0)

__block_impl結構體

下面我們著重來看__block_impl結構體,在cpp文件中搜索__block_impl

__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(三)
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容