Block原理(一)

Block究竟是什么,我們先從c++代碼開始

從一個(gè)最簡(jiǎn)單的block結(jié)構(gòu)開始

image.png

clang -rewrite-objc main.m -o main.cpp && open main.cpp

image.png
image.png

為了方便閱讀 我們簡(jiǎn)化一下代碼

image.png

為了方便進(jìn)一步閱讀,這里對(duì)其中的命名做了簡(jiǎn)化,參考下面的簡(jiǎn)單流程

image.png
  • 結(jié)合clang編譯中間c++代碼,通過block的創(chuàng)建,結(jié)合上圖, 腦子里先勾勒一個(gè)sketch

    • 創(chuàng)建兩層結(jié)構(gòu)

      • BlockCreate 結(jié)構(gòu)

      • Block結(jié)構(gòu),屬于BlockCreate的成員

    • 通過BlockCreate構(gòu)造傳參,實(shí)例化BlockCreate成員Block::block

    • 最終返回的是一個(gè) BlockCreate結(jié)構(gòu)指針

    • 通過BlockCreate結(jié)構(gòu)首地址,我們可以拿到成員Block::block, BlockCreate首地址與 成員block首地址是一樣的,因?yàn)閎lock位于 BlockCreate內(nèi)存空間的起始處

      既然可以拿到首地址(成員block地址),那么同樣可以通過內(nèi)存偏移拿到成員Desc的地址

    • 通過拿到成員Block::block地址,就可以調(diào)用Block::block的成員方法FuncPtr了, 而FuncPtr恰恰是通過 BlockCreate構(gòu)造實(shí)例化Block::block成員時(shí) 賦值的fun函數(shù)入口地址

  • 一定要了解這個(gè)兩層結(jié)構(gòu),雖然不是真正意義源碼,但是對(duì)后面我們分析源碼很有幫助

前面的例子沒有使用變量,我們可以通過前面的方式再操作一次,對(duì)比下區(qū)別

image.png
image.png

當(dāng)自定義的結(jié)構(gòu)體內(nèi)部訪問外部的一個(gè)局部變量時(shí)

你會(huì)發(fā)現(xiàn)clang生成的c++代碼發(fā)生了變化 對(duì)照上面的那個(gè)實(shí)例化流程

  • BlockCreate 結(jié)構(gòu)體內(nèi)多了一個(gè)成員 int a

  • BlockCreate構(gòu)造 也多了一個(gè)傳參

  • func函數(shù)內(nèi)部訪問變量 a通過 func(BlockCreate self) 的 BlockCreate::self 來(lái)獲取拷貝

  • 你會(huì)發(fā)現(xiàn)有3處存在變量a

    • main函數(shù)內(nèi)的局部變量a

    • BlockCrete 結(jié)構(gòu)體內(nèi)的成員變量a

    • func方法內(nèi)部的局部變量a

    其實(shí)這3個(gè)變量a分別是3個(gè)不同的變量了

把局部變量a改為static修飾,繼續(xù)clang c++查看

image.png
image.png

用static修飾變量a,不一樣了

BlockCreate構(gòu)造傳參,此時(shí)傳遞的是 a的地址,而BlockCreate成員 a也變成了 指針, func內(nèi)部的局部變量a 也變成了 指針,func內(nèi)部的a是通過 BlockCreate::*self 的指針a 賦值 給func內(nèi)部的局部變量 指針a

所以static修飾a后,func內(nèi)部訪問的a其實(shí)還是 main函數(shù)內(nèi)部的 指針a

把局部變量a改為 __block修飾,繼續(xù)clang c++查看

image.png
image.png

希望你不會(huì)覺得懵,這次復(fù)雜了些

  • 出現(xiàn)了一個(gè)結(jié)構(gòu) __Block_byref_a_0

  • BlockCreate 成員Desc的結(jié)構(gòu)內(nèi)部多了兩個(gè) 函數(shù) copy & dispose

這里簡(jiǎn)單解釋下

  • 普通的局部變量a 變成了一個(gè)結(jié)構(gòu) __Block_byref_a_0, a是這個(gè)結(jié)構(gòu)的成員

    • 成員 void *__isa

    • 成員 __block_byref_a_0 *__forwarding;

    • 成員 int __flags;

    • 成員 int __size

    • 成員 int a

    __isa 從前面的截圖可知。是一個(gè)_NSConcreteStackBlock 也就是棧block指針

在main里聲明的__block修飾的局部變量, 地址賦值給了 __forwarding, 值賦給了 Block_byref結(jié)構(gòu)里的成員a,注意這個(gè)設(shè)定, 雖然成員也叫a,只是起到一個(gè)接收值的作用,關(guān)鍵在于__forwarding 拿到了原來(lái)的a的指針

先看下__block修飾的a究竟是怎么訪問的
image.png
__forwarding 類型 __Block_byref_a_0 *,類似于鏈表節(jié)點(diǎn),所以也是一個(gè)指向 __Block_byref_a_0 結(jié)構(gòu)的指針 至于有什么用,暫存疑,后面源碼接著分析

對(duì)比著看,其實(shí)很明顯,不難理解
image.png

block源碼 - libclosure-79 查看

源碼入口該怎么查看呢,我們先通過匯編看下

image.png

既然retainBlock,說明block開辟了空間,進(jìn)入查看

image.png

繼續(xù)跳轉(zhuǎn) br x16

image.png

目前找到了_Block_copy這樣一個(gè)符號(hào),然后進(jìn)入源碼查看

image.png

你會(huì)看到一個(gè)結(jié)構(gòu)Block_layout

image.png

Block_layout 就是前面通過clang c++代碼 分析出的 兩層結(jié)構(gòu)BlockCreate成員 Block::block

__block 修飾變量 測(cè)試代碼放進(jìn) block源碼進(jìn)行調(diào)試

image.png

這段代碼是在block源碼中測(cè)試的

image.png

這其實(shí)就是依照Block_layout 棧上的空間結(jié)構(gòu),在堆區(qū)創(chuàng)建了一個(gè)Block_layout結(jié)構(gòu)

同時(shí) 新開辟的Block_layout結(jié)構(gòu)->invoke 從原來(lái)?xiàng)I螧lock_layout->invoke拷貝過來(lái)

image.png

既然是堆上開辟空間創(chuàng)建的Block_layout結(jié)構(gòu),自然isa 指向 _NSConcreteMallocBlock (堆block)

block分析源碼遇到問題

現(xiàn)在還有兩塊沒探索到源碼,就是 前面通過clang 編譯生成的c++代碼中__Block_byref_a_0這樣的結(jié)構(gòu),還有一塊是BlockCreate構(gòu)造邏輯部分

那么接下來(lái)該何去何從?

我選擇最原始的方式 匯編 + 下符號(hào)斷點(diǎn) + 結(jié)合clang c++代碼分析

image.png

先把代碼斷到此處,防止dyld其他流程干擾

image.png

下符號(hào)斷點(diǎn) 同時(shí)把前面分析過的 _Block_copy 符號(hào)也下下來(lái),為了方便分析流程

跟著調(diào)試 進(jìn)入 _Block_object_dispose:

image.png

回到之前clang編譯出的c++代碼看下

image.png

既然下到了符號(hào)_Block_object_dispose 那么同樣也把符號(hào) _Block_object_copy下下來(lái)繼續(xù)調(diào)試

沒有的話 就試試 _Block_object_assign, 之所以沒有找到 _Block_object_copy符號(hào),是因?yàn)槟鞘怯删幾g器決定的

成功斷點(diǎn)符號(hào) _Block_object_assign

image.png

找到頭緒,自然我們又回到了源碼

image.png
  • 看下源碼注釋

    When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point to do the assignment.

    當(dāng)Blocks(可以理解為前面的有成員func的那個(gè)結(jié)構(gòu)) 或者 Block_byref持有對(duì)象時(shí)候,這個(gè)入口就會(huì)被觸發(fā) 執(zhí)行賦值操作

image.png
  • __block int a = 10 類型為 BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK or BLOCK_FIELD_IS_BYREF

    執(zhí)行 _Block_byref_copy()

_Block_byref_copy

在分析_Block_byref_copy流程之前,我們需要了解下Block_byref 是什么

image.png

從前面clang編譯拿到的c++代碼,可以看到,Block_byref 是對(duì)常規(guī)變量的封裝,封裝結(jié)構(gòu)里還多了isa,__forwarding成員

image.png

源碼中還存在 Block_byref_2 Block_byref_3 兩個(gè)結(jié)構(gòu),暫且不表,后面會(huì)繼續(xù)說明

我們可以做個(gè)假設(shè),目前我們測(cè)試的實(shí)例 是block引用外部 __block修飾的變量,我們也是這么用的,既然block內(nèi)部訪問外部變量,那么也會(huì)對(duì)于這個(gè)變量的引用計(jì)數(shù)產(chǎn)生影響 flags就是存儲(chǔ)引用計(jì)數(shù)的

_Block_byref_copy翻譯

image.png

如果源byref結(jié)構(gòu)已經(jīng)在heap上,則不需要執(zhí)行拷貝,引用計(jì)數(shù)+1

image.png

中間有一段內(nèi)存偏移的代碼,還沒解析,繼續(xù)

image.png

byref_keep byref_destroy 究竟實(shí)現(xiàn)了什么功能

因?yàn)槲覀冇玫某R?guī)變量a測(cè)試 我們換成object看下

將變量a換為object測(cè)試

image.png

clang c++代碼

image.png
image.png

從源碼得知

image.png
image.png
image.png

131有什么意義

image.png

兩個(gè)參數(shù) + 40 什么意思

image.png

按照編譯的邏輯,byref_keep 就是 object類型的對(duì)象的 拷貝

但是運(yùn)行時(shí)會(huì)做修正 流程有差別

同樣 byref_destroy:

image.png

以上為 Block_byref 邏輯,再通過clang得到的c++ 看下 Block_layout 的處理

image.png
image.png

再確認(rèn)下 __block修飾的 object對(duì)象,在block體里 究竟是如何訪問的

image.png

總結(jié)

  • __block 修飾變量之后,編譯器會(huì)在棧上構(gòu)建一個(gè) 棧Block_byref(包含變量指針)

  • 定義block,可以理解為編譯器生成一個(gè)中間結(jié)構(gòu)BlockCreate(這個(gè)名字是特意起的,知道是個(gè)結(jié)構(gòu),為了便于理解,你可以這么理解)

    • 同時(shí)編譯器會(huì)在棧上初始化構(gòu)建一個(gè) 棧Block_layout(包含func成員)
  • 執(zhí)行BlockCreate構(gòu)造方法

    • 通過Block_layout首地址偏移 得到 Block_copy函數(shù)地址, 執(zhí)行Block_copy,把 棧Block_byref 拷貝 到堆Block_byref

    • 構(gòu)造參數(shù) 棧Block_byref,通過Block_byref首地址偏移 得到 Block_byref_2(包含_Block_byref_copy 即byref拷貝函數(shù))首地址, 執(zhí)行 _Block_byref_copy函數(shù), 把棧Block_byref 拷貝到 堆Block_byref

    • 繼續(xù)上一步的位置 內(nèi)存偏移 8字節(jié),得到堆上開辟的 object內(nèi)存空間首地址, 這里當(dāng)然就存放 object對(duì)象了

    • 需要注意的一個(gè)細(xì)節(jié) 棧Block_byref 拷貝到 堆Block_byref之后,由于堆上是新的內(nèi)存空間,那么棧與堆不就兩個(gè)空間了嗎,如何保障訪問的是同一塊內(nèi)存?

      就覺辦法就是,在拷貝之后, 把 棧Block_byref 和 堆Block_byref 里的forwarding 都指向了 堆Block_byref, 也就是 堆 forwarding再指向一遍自己

      __block修飾變量之后,不管是在block塊內(nèi)訪問變量,還是在block外訪問變量,都是通過 forwarding訪問到堆空間,然后再訪問目標(biāo)空間內(nèi)的 變量, 這樣就保障了 訪問的變量是同一塊內(nèi)存空間

image.png
image.png
image.png
  • Block_byref 持有的變量生命周期結(jié)束,執(zhí)行 _Block_object_dispose

    • 執(zhí)行_Block_byref_release函數(shù),根據(jù)Block_byref 首地址偏移 找到 Block_byref_2首地址,繼續(xù)偏移8字節(jié) 得到 byref_destroy 執(zhí)行析構(gòu) 回收堆內(nèi)存空間
  • Block_layout 作用域結(jié)束 或者 生命周期結(jié)束, 執(zhí)行 _Block_release

    • 根據(jù) Block_layout 首地址偏移 找到 Block_descriptor_2 首地址,繼續(xù)偏移8字節(jié),的到 dispose 執(zhí)行析構(gòu) 回收 堆上開辟的 Block_layout 堆內(nèi)存空間

讀取寄存器查看

image.png

符號(hào)斷點(diǎn) _Block_copy

image.png

_Block_copy 執(zhí)行之前,寄存器rax接收參數(shù) (arm64 讀取寄存器x1)

image.png

執(zhí)行完之后,ret返回,rax寄存器存儲(chǔ)返回值

image.png
  • 變量a 改為 __block修飾
image.png

因?yàn)開_block修飾,Block_layout 中就出現(xiàn)了 copy 函數(shù)地址,通過copy,就執(zhí)行 _Block_copy

而沒有__block修飾,沒有 copy dispose 函數(shù),默認(rèn)執(zhí)行 _Block_copy

造成這個(gè)差別就在于 構(gòu)造傳參時(shí)候 flag的區(qū)別,__block修飾之前 是0,__block修飾之后, 1 << 25

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • iOS之武功秘籍 文章匯總[http://www.itdecent.cn/p/07991e5b1c30] 寫在前...
    長(zhǎng)茳閱讀 704評(píng)論 0 4
  • 摘要block是2010年WWDC蘋果為Objective-C提供的一個(gè)新特性,它為我們開發(fā)提供了便利,比如GCD...
    西門吹雪123閱讀 1,001評(píng)論 0 4
  • 一、block簡(jiǎn)介 1、block的三種類型 NSGlobalBlock - 全局 block NSMallocB...
    _zhang__閱讀 552評(píng)論 0 1
  • Block本質(zhì)上也是一個(gè)OC對(duì)象,它內(nèi)部也有個(gè)isa指針,它是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對(duì)象。閉包 = ...
    正_文閱讀 1,320評(píng)論 1 10
  • 1 基本說明 Block一直是OC的一個(gè)重點(diǎn)、難點(diǎn)、黑科技。Block在日常項(xiàng)目中經(jīng)常使用,他的實(shí)現(xiàn)方式和一般的o...
    NS西北風(fēng)閱讀 551評(píng)論 0 3

友情鏈接更多精彩內(nèi)容