我們使用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型別):
structisa指標說明block可以成為乙個objc物件。__block_impl ;
__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();其轉碼後的語句:
((將blk這個函式指標型別強轉為__block_impl型別,然後取其執行函式指標,然後將此指標型別強轉為返回void*並接收乙個__block_impl*的函式指標,最後呼叫這個函式,傳入強轉為__block_impl*型別的blk,void
(*)(__block_impl *))((__block_impl *)blk)->funcptr)((__block_impl *)blk);
即呼叫了前述的函式__main_block_func_0
4、objective-c類成員函式中的block
原始碼如下:
- (這裡我故意將self賦值給oj這個變數,是為了驗證前一章提出的乙個結論:無法通過簡單的間接引用self來防止retain迴圈,要避免迴圈,我們需要__block標記(多謝樓下網友的提醒)void
)of1
; block_copy(oblk);
}
轉碼如下:
structobjc方法中的block與c中的block並無太多差別,只是一些標記值可能不同,為了標記其是objc方法中的blcok。__obj1__of1_block_impl_0
}; static
void
__obj1__of1_block_func_0(
struct
__obj1__of1_block_impl_0 *__cself)
注意其建構函式的引數:obj1 *_oj
這個_oj在block複製到heap時,會被retain,而_oj與self根本就是相等的,所以,最終retain的就是self,所以如果當前例項持有了這個block,retain迴圈就形成了。
而一旦為其增加了__block標記:
- (clang為我們的bself結構體建立了自己的copy/dispose輔助函式,33554432(即1<<25 block_has_copy_dispose)這個值告訴系統,我們的bself結構體具有copy/dispose輔助函式。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 = ;
而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...