iOS中block介紹(三)揭開神秘面紗 上

2021-06-22 09:22:08 字數 3644 閱讀 1215

我們使用clang的rewrite-objc命令來獲取轉碼後的**。

1、block的底層實現

我們來看看最簡單的乙個block:

這個block僅僅列印棧變數i和j的值,其被clang轉碼為:

首先是乙個結構體__main_block_impl_0(從圖二中的最後一行可以看到,block是乙個指向__main_block_impl_0的指標,初始化後被型別強轉為函式指標),其中包含的__block_impl是乙個公共實現(學過c語言的同學都知道,__main_block_impl_0的這種寫法表示其可以被型別強轉為__block_impl型別):

struct

__block_impl ;

isa指標說明block可以成為乙個objc物件。

__main_block_impl_0的意思是main函式中的第0個block的implementation,這就是這個block的主體了。

這個結構體的建構函式的引數:

block實際執行**所在的函式的指標,當block真正被執行時,實際上是呼叫了這個函式,其命名也是類似的方式。

block的描述結構體,注意這個結構體宣告結束時就建立了乙個唯一的desc,這個desc包含了block的大小,以及複製和析構block時需要額外呼叫的函式。

接下來是block所引用到的變數們

最後是乙個標記值,內部實現需要用到的。(我用計算器看了一下,570425344這個值等於1<<29,即block_has_descriptor這個列舉值)

所以,我們可以看到:

帶有__block標記的變數會被取位址來傳入建構函式,為修改其值奠定了基礎

接下來是block執行函式__main_block_func_0:

其唯一的引數是__main_block_impl_0的指標,我們看到printf語句的資料**都取自__cself這個指標,比較有意思的是i的取值方式(帶有__block標記的變數i被轉碼為乙個結構體),先取__forward指標,再取i,這為將i複製到堆中奠定了基礎。

再下來是預定義好的兩個複製/釋放輔助函式,其作用後面會講到。 

最後是block的描述資訊結構體 __main_block_desc_0,其包含block的記憶體占用長度,已經複製/釋放輔助函式的指標,其宣告結束時,就建立了乙個名為__main_block_desc_0_data的結構體,我們看它構造時傳入的值,這個data結構體的作用就一目了然了:

長度用sizeof計算,輔助函式的指標分別為上面預定義的兩個輔助函式。

注意,如果這個block沒有使用到需要在block複製時進行copy/retian的變數,那麼desc中不會有輔助函式

至此,乙個block所有的部件我們都看齊全了,乙個主體,乙個真正的執行**函式,乙個描述資訊(可能包含兩個輔助函式)。

2、構造乙個block

我們進入main函式:

圖一中的第三行(block的宣告),在圖二中,轉化為乙個函式指標的宣告,並且都沒有被賦予初始值。

而圖一中的最後一行(建立乙個block),在圖二中,成為了對__main_block_impl_0的建構函式的呼叫,傳入的引數的意義上面我們已經講過了。

所以構造乙個block就是建立了__main_block_impl_0 這個c++類的例項。

3、呼叫乙個block

呼叫乙個block的寫法很簡單,與呼叫c語言函式的語法一樣:

blk(); 

其轉碼後的語句:

((

void

(*)(__block_impl *))((__block_impl *)blk)->funcptr)((__block_impl *)blk); 

將blk這個函式指標型別強轉為__block_impl型別,然後取其執行函式指標,然後將此指標型別強轉為返回void*並接收乙個__block_impl*的函式指標,最後呼叫這個函式,傳入強轉為__block_impl*型別的blk,

即呼叫了前述的函式__main_block_func_0

4、objective-c類成員函式中的block

原始碼如下:

- (

void

)of1 

; block_copy(oblk); 

這裡我故意將self賦值給oj這個變數,是為了驗證前一章提出的乙個結論:無法通過簡單的間接引用self來防止retain迴圈,要避免迴圈,我們需要__block標記(多謝樓下網友的提醒)

轉碼如下:

struct

__obj1__of1_block_impl_0  

}; static

void

__obj1__of1_block_func_0(

struct

__obj1__of1_block_impl_0 *__cself)  

objc方法中的block與c中的block並無太多差別,只是一些標記值可能不同,為了標記其是objc方法中的blcok。

注意其建構函式的引數:obj1 *_oj

這個_oj在block複製到heap時,會被retain,而_oj與self根本就是相等的,所以,最終retain的就是self,所以如果當前例項持有了這個block,retain迴圈就形成了。

而一旦為其增加了__block標記:

- (

void

)of1 

; }其轉碼則變為: 

//增加了如下行

struct

__block_byref_bself_0 ; 

static

void

__block_byref_id_object_copy_131(

void

*dst, 

void

*src)  

static

void

__block_byref_id_object_dispose_131(

void

*src)  

//宣告處變為

__block __block_byref_bself_0 bself = ; 

clang為我們的bself結構體建立了自己的copy/dispose輔助函式,33554432(即1<<25 block_has_copy_dispose)這個值告訴系統,我們的bself結構體具有copy/dispose輔助函式。

而131這個引數(二進位制1000 0011,即block_field_is_object (3) |block_byref_caller(128))

中的block_byref_caller在內部實現中告訴系統不要進行retain或者copy,

也就是說,在 __block bself 被複製至heap上時,系統會發現有輔助函式,而輔助函式呼叫後,並不retain或者copy 其結構體內的bself。

這樣就避免了迴圈retain。

當我們建立乙個block,並呼叫之,編譯器為我們做的事情如下:

1.建立block所有的部件**:乙個主體,乙個真正的執行**函式,乙個描述資訊(可能包含兩個輔助函式)。

2.將我們的建立**轉碼為block_impl的構造語句。

3.將我們的執行語句轉碼為對block的執行函式的呼叫。

iOS中block介紹(三)揭開神秘面紗 上

ad 51cto 網 第十二期沙龍 大話資料之美 如何用資料驅動使用者體驗 我們使用clang的rewrite objc命令來獲取轉碼後的 1 block的底層實現 我們來看看最簡單的乙個block 這個block僅僅列印棧變數i和j的值,其被clang轉碼為 首先是乙個結構體 main block...

block介紹(三)揭開神秘面紗(上)

lock到底是什麼 我們使用clang的rewrite objc命令來獲取轉碼後的 1 block的底層實現 我們來看看最簡單的乙個block 圖一這個block僅僅列印棧變數i和j的值,其被clang轉碼為 圖二首先是乙個結構體 main block impl 0 從圖二中的最後一行可以看到,bl...

iOS中Block介紹(一)基礎

block是c級別的語法和執行時特性。block比較類似c函式,但是block比之c函式,其靈活性體現在棧記憶體 堆記憶體的引用,我們甚至可以將乙個block作為引數傳給其他的函式或者block。先看乙個比較簡單的block例子 int multiplier 7 int myblock int in...