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

2021-06-18 19:13:45 字數 4684 閱讀 5134

lock到底是什麼

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

1block的底層實現

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

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

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

[objc]view plaincopy

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語言函式的語法一樣:

[objc]view plaincopy

blk();  

其轉碼後的語句:

[objc]view plaincopy

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

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

即呼叫了前述的函式__main_block_func_0

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

原始碼如下:

[objc]view plaincopy

- (void)of1  

;  block_copy(oblk);  

}  

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

轉碼如下:

[objc]view plaincopy

struct__obj1__of1_block_impl_0   

};  

staticvoid__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標記:

[objc]view plaincopy

- (void)of1  

;  }  

其轉碼則變為:

[objc]view plaincopy

//增加了如下行

struct__block_byref_bself_0 ;  

staticvoid__block_byref_id_object_copy_131(voidvoid*dst,voidvoid*src)   

staticvoid__block_byref_id_object_dispose_131(voidvoid*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介紹(三)揭開神秘面紗 上

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

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

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

揭開信貸神秘面紗

貸款信用保險是指保險人對銀行或其他金融機構與企業之間的借貸合同進行擔保,以承保借款人信譽風險的保險。在貸款信用保險中,貸款方 即債權人 是投保人。但保單簽發後,貸款方即成為被保險人。當企業無法歸還貸款時,債權人可以從保險那裡獲得補償。貸款人在獲得保險人的補償後,必須將債權轉讓給保險人,由保險人向借款...