block沒那麼難(一):block的實現

發表於2016-06-20

本系列博文總結自《Pro Multithreading and Memory Management for iOS and OS X with ARC》


block 顧名思義就是程式碼塊,將同一邏輯的程式碼放在一個塊,使程式碼更簡潔緊湊,易於閱讀,而且它比函式使用更方便,程式碼更美觀,因而廣受開發者歡迎。但同時 block 也是 iOS 開發中坑最多的地方之一,因此有必要了解下 block 的實現原理,知其然,更知其所以然,才能從根本上避免挖坑和踩坑。

需要知道的是,block 只是 Objective-C 對閉包的實現,並不是 iOS 獨有的概念,在 C++、Java 等語言也有實現閉包,名稱不同而已。

特別宣告

以下研究所用的過程程式碼由 clang 編譯前端生成,僅作理解之用。實際上 clang 根本不會將 block 轉換成人類可讀的程式碼,它對 block 到底做了什麼,誰也不知道。

所以,切勿將過程程式碼當做block的實際實現,切記切記!!!


將下面的 test.m 程式碼用 clang 工具翻譯 test.cpp 程式碼

test.m 程式碼

test.cpp


接著,我們逐一來看下這些函式和結構體

block 結構體資訊詳解

struct __block_impl

  • isa
    指向例項物件,表明 block 本身也是一個 Objective-C 物件。block 的三種型別:_NSConcreteStackBlock_NSConcreteGlobalBlock_NSConcreteMallocBlock,即當程式碼執行時,isa 有三種值
  • Flags
    按位承載 block 的附加資訊;
  • Reserved
    保留變數;
  • FuncPtr
    函式指標,指向 Block 要執行的函式,即{ printf("Block\n") };

struct __main_block_impl_0

  • impl
    block 實現的結構體變數,該結構體前面已說明;
  • Desc
    描述 block 的結構體變數;
  • __main_block_impl_0
    結構體的建構函式,初始化結構體變數 implDesc

static void __main_block_func_0

static struct __main_block_desc_0

  • reserved
    結構體資訊保留欄位
  • Block_size
    結構體大小

此處已定義了一個該結構體型別的變數 __main_block_desc_0_DATA


block 實現的執行流程

Created with Raphaël 2.1.2main()呼叫 __main_block_impl_0 建構函式初始化結構體__main_block_impl_0(__main_block_func_0 , __main_block_desc_0_DATA);得到的__main_block_impl_0 型別變數賦值給 blk執行 blk->FuncPtr()函式,即 printf(“Block\n”);End

最基礎的 block 實現就這麼簡單。


接著再看 block 獲取外部變數

block 獲取外部變數

執行下面的程式碼

列印結果

intValue = 1

和第一段原始碼不同的是,這裡多了個區域性變數 intValue,而且還在 block 裡面獲取到了。

通過前一段對 block 原始碼的學習,我們已經瞭解到 block 的函式定義在 main() 函式之外,那它又是如何獲取 main() 裡面的區域性變數呢?為了解開疑惑,我們再次用 clang 重寫這段程式碼

原來 block 通過引數值傳遞獲取到 intValue 變數,通過函式

__main_block_impl_0 (void *fp, struct __main_block_desc_0 *desc, int _intValue, int flags=0) : intValue(_intValue)

儲存到 __main_block_impl_0 結構體的同名變數 intValue,通過程式碼 int intValue = __cself->intValue; 取出 intValue,列印出來。

建構函式 __main_block_impl_0 冒號後的表示式 intValue(_intValue) 的意思是,用 _intValue 初始化結構體成員變數 intValue

有四種情況下應該使用初始化表示式來初始化成員:
1:初始化const成員
2:初始化引用成員
3:當呼叫基類的建構函式,而它擁有一組引數時
4:當呼叫成員類的建構函式,而它擁有一組引數時

參考:C++類成員冒號初始化以及建構函式內賦值

至此,我們已經瞭解了block 的實現,以及獲取外部變數的原理。但是,我們還不能在 block 內修改 intValue 變數。如果你有心試下,在 block 內部修改 intValue 的值,會報編譯錯誤

Variable is not assignable(missing __block type specifier)

那麼如何在 block 內修改外部變數呢,請看下篇 block沒那麼難(二):block 和變數的記憶體管理

相關文章