(譯)窺探Blocks(2)

本文翻譯自Matt Galloway的博客

之前的文章(譯)窺探Blocks(1)我們已經(jīng)了解了block的內(nèi)部原理,以及編譯器如何處理它。本文我將討論一下非常量的blocks以及它們?cè)跅I系慕M織方式。

Block 類型

第一篇文章中,我們看到block有__NSConcreteGlobalBlock類。block結(jié)構(gòu)體和descriptor都在編譯階段基于已知的變量完全初始化了。block還有一些不同的類型,每一個(gè)類型都對(duì)應(yīng)一個(gè)相關(guān)的類。為了簡(jiǎn)單起見,我們只考慮其中的三個(gè):

  1. _NSConcreteGlobalBlock是一個(gè)全局定義的block,在編譯階段就完成創(chuàng)建工作。這些block沒有捕獲任何域,比如一個(gè)空block。
  2. _NSConcreteStackBlock是一個(gè)在棧上的block,這是所有blocks在最終拷貝到堆上之前所開始的地方。
  3. _NSConcreteMallocBlock是一個(gè)在堆上的block,這是拷貝一個(gè)block后最終的位置。它們?cè)谶@里被引用計(jì)數(shù)并且在引用計(jì)數(shù)變?yōu)?時(shí)被釋放。

捕獲域的block

現(xiàn)在我們來看看下面一段代碼:

#import <dispatch/dispatch.h>

typedef void(^BlockA)(void);
void foo(int);

__attribute__((noinline))
void runBlockA(BlockA block) {
    block();
}

void doBlockA() {
    int a = 128;
    BlockA block = ^{
        foo(a);
    };
    runBlockA(block);
}

這里有一個(gè)方法foo,因此block捕獲了一些東西,用一個(gè)捕獲到的變量來調(diào)用方法。我又看了一下armv7所產(chǎn)生的一小段相關(guān)代碼:

    .globl  _runBlockA
    .align  2
    .code   16                      @ @runBlockA
    .thumb_func     _runBlockA
_runBlockA:
    ldr     r1, [r0, #12]
    bx      r1 

首先,runBlockA方法與之前的結(jié)果一樣,它調(diào)用block的invoke方法。然后看看doBlockA

.globl  _doBlockA
    .align  2
    .code   16                      @ @doBlockA
    .thumb_func     _doBlockA
_doBlockA:
    push    {r7, lr}
    mov     r7, sp
    sub     sp, #24
    movw    r2, :lower16:(L__NSConcreteStackBlock$non_lazy_ptr-(LPC1_0+4))
    movt    r2, :upper16:(L__NSConcreteStackBlock$non_lazy_ptr-(LPC1_0+4))
    movw    r1, :lower16:(___doBlockA_block_invoke_0-(LPC1_1+4))
LPC1_0:
    add     r2, pc
    movt    r1, :upper16:(___doBlockA_block_invoke_0-(LPC1_1+4))
    movw    r0, :lower16:(___block_descriptor_tmp-(LPC1_2+4))
LPC1_1:
    add     r1, pc
    ldr     r2, [r2]
    movt    r0, :upper16:(___block_descriptor_tmp-(LPC1_2+4))
    str     r2, [sp]
    mov.w   r2, #1073741824
    str     r2, [sp, #4]
    movs    r2, #0
LPC1_2:
    add     r0, pc
    str     r2, [sp, #8]
    str     r1, [sp, #12]
    str     r0, [sp, #16]
    movs    r0, #128
    str     r0, [sp, #20]
    mov     r0, sp
    bl      _runBlockA
    add     sp, #24
    pop     {r7, pc}

這下看起來比之前的復(fù)雜多了。與從一個(gè)全局符號(hào)加載一個(gè)block不同,這看起來做了許多工作。看起來可能有點(diǎn)麻煩,但其實(shí)也非常簡(jiǎn)單。我們最好考慮重新整理這些方法,但請(qǐng)相信我這樣做不會(huì)沒有改變?nèi)魏喂δ堋>幾g器之所以這樣安排它的指令順序,是為了優(yōu)化編譯性能,減少流水線氣泡。重新整理后的方法如下:

_doBlockA:
        // 1
        push    {r7, lr}
        mov     r7, sp

        // 2
        sub     sp, #24

        // 3
        movw    r2, :lower16:(L__NSConcreteStackBlock$non_lazy_ptr-(LPC1_0+4))
        movt    r2, :upper16:(L__NSConcreteStackBlock$non_lazy_ptr-(LPC1_0+4))
LPC1_0:
        add     r2, pc
        ldr     r2, [r2]
        str     r2, [sp]

        // 4
        mov.w   r2, #1073741824
        str     r2, [sp, #4]

        // 5
        movs    r2, #0
        str     r2, [sp, #8]

        // 6
        movw    r1, :lower16:(___doBlockA_block_invoke_0-(LPC1_1+4))
        movt    r1, :upper16:(___doBlockA_block_invoke_0-(LPC1_1+4))
LPC1_1:
        add     r1, pc
        str     r1, [sp, #12]

        // 7
        movw    r0, :lower16:(___block_descriptor_tmp-(LPC1_2+4))
        movt    r0, :upper16:(___block_descriptor_tmp-(LPC1_2+4))
LPC1_2:
        add     r0, pc
        str     r0, [sp, #16]

        // 8
        movs    r0, #128
        str     r0, [sp, #20]

        // 9
        mov     r0, sp
        bl      _runBlockA

        // 10
        add     sp, #24
        pop     {r7, pc}

這就是它所做的事:

  1. 方法開始。r7被壓入棧,因?yàn)樗磳⒈恢貙懀易鳛橐粋€(gè)寄存器必須在方法調(diào)用時(shí)候保存值。lr是一個(gè)鏈接寄存器,也被壓入棧,保存了下一個(gè)指令的地址,好讓方法返回時(shí)繼續(xù)執(zhí)行下一個(gè)指令。可以在方法結(jié)尾看到。 棧指針(sp)也被保存在r7中。

  2. 棧指針(sp)減去24,留出24字節(jié)的棧空間存儲(chǔ)數(shù)據(jù)。

  3. 這一小塊代碼正在相對(duì)于程序計(jì)數(shù)器查找L__NSConcreteStackBlock$non_lazy_ptr符號(hào),這樣最后鏈接成功的二進(jìn)制文件,不管代碼結(jié)束于任何地方,它都可以正常工作(這句話有點(diǎn)繞,翻譯的不好,需要好好理解一下)。這個(gè)值最后存儲(chǔ)在棧指針指向的位置。

  4. 1073741824存儲(chǔ)在sp + 4 的位置上。

  5. 0存儲(chǔ)在sp + 8的位置上。現(xiàn)在可能情況比較清晰了。回顧上一篇文章中提到的Block_layout結(jié)構(gòu)體,可以看出一個(gè)Block_layout結(jié)構(gòu)體在棧上創(chuàng)建了!目前為止已經(jīng)有了isa指針,flagsreserved值被設(shè)置了。

  6. ___doBlockA_block_invoke_0的地址存儲(chǔ)在sp + 12位置。這就是block結(jié)構(gòu)體的invoke參數(shù)。

  7. ___block_descriptor_tmp的地址存儲(chǔ)在sp + 16位置。這就是block結(jié)構(gòu)體的descriptor參數(shù)。

  8. 128存儲(chǔ)在sp + 20的位置。啊!如果你回看Block_layout結(jié)構(gòu)體你會(huì)發(fā)現(xiàn)里面只有5個(gè)值。那么存在這個(gè)結(jié)構(gòu)體末尾的是什么呢?哈哈,別忘記了,這個(gè)128就是在這個(gè)block前定義的、被block捕獲的值。所以這一定是存儲(chǔ)它們使用變量的地方——在Block_layout最后。

  9. sp現(xiàn)在指向一個(gè)完全初始化的block結(jié)構(gòu)體,它被放入r0寄存器,然后runBlockA被調(diào)用。(記住在ARM EABI中r0包含了方法的第一個(gè)參數(shù))

  10. 最后sp + 24 已抵消最開始減去的24。然后分別從棧彈出兩個(gè)值到r7pc中。r7抵消一開始?jí)簵5牟僮鳎?code>pc將獲得方法開始時(shí)lr里面的值。這樣有效地完成了方法返回的操作,讓CPU繼續(xù)(程序計(jì)數(shù)器pc)從方法返回的地方(鏈接寄存器lr)執(zhí)行。

哇哦!你還在跟著我學(xué)?太牛逼啦!

這一小段的最后一部分是來看看invoke方法和descriptor長(zhǎng)什么樣。我們希望它們不要與第一篇文章中的全局block差太多。

.align  2
    .code   16                      @ @__doBlockA_block_invoke_0
    .thumb_func     ___doBlockA_block_invoke_0
___doBlockA_block_invoke_0:
    ldr     r0, [r0, #20]
    b.w     _foo

    .section        __TEXT,__cstring,cstring_literals
L_.str:                                 @ @.str
    .asciz   "v4@?0"

    .section        __TEXT,__objc_classname,cstring_literals
L_OBJC_CLASS_NAME_:                     @ @"\01L_OBJC_CLASS_NAME_"
    .asciz   "\001P"

    .section        __DATA,__const
    .align  2                       @ @__block_descriptor_tmp
___block_descriptor_tmp:
    .long   0                       @ 0x0
    .long   24                      @ 0x18
    .long   L_.str
    .long   L_OBJC_CLASS_NAME_

還真是相差不大。唯一的區(qū)別在于block descriptor的size值。現(xiàn)在它是24而不是20。因?yàn)閎lock此時(shí)捕獲了一個(gè)整形數(shù)值。我們已經(jīng)看到在創(chuàng)建block結(jié)構(gòu)體時(shí),這額外的4字節(jié)被放在了最后。

同樣地,你在實(shí)際執(zhí)行的方法__doBlockA_block_invoke_0中也會(huì)發(fā)現(xiàn)參數(shù)值從結(jié)構(gòu)體末尾處(r0 + 20)讀取出來,這就是block捕獲的值。

捕獲對(duì)象類型的值會(huì)怎樣?

下面要考慮的是捕獲的不再是一個(gè)整形,而是一個(gè)對(duì)象,比如NSString。欲知詳情,請(qǐng)看下面代碼:

#import <dispatch/dispatch.h>

typedef void(^BlockA)(void);
void foo(NSString*);

__attribute__((noinline))
void runBlockA(BlockA block) {
    block();
}

void doBlockA() {
    NSString *a = @"A";
    BlockA block = ^{
        foo(a);
    };
    runBlockA(block);
}

我不再研究doBlockA的細(xì)節(jié),因?yàn)樽兓淮蟆1容^有意思的是它創(chuàng)建的block descriptor結(jié)構(gòu)體。

 .section        __DATA,__const
    .align  4                       @ @__block_descriptor_tmp
___block_descriptor_tmp:
    .long   0                       @ 0x0
    .long   24                      @ 0x18
    .long   ___copy_helper_block_
    .long   ___destroy_helper_block_
    .long   L_.str1
    .long   L_OBJC_CLASS_NAME_

注意現(xiàn)在有了名為___copy_helper_block____destroy_helper_block_的函數(shù)指針。這里是這些函數(shù)的定義:

.align  2
    .code   16                      @ @__copy_helper_block_
    .thumb_func     ___copy_helper_block_
___copy_helper_block_:
    ldr     r1, [r1, #20]
    adds    r0, #20
    movs    r2, #3
    b.w     __Block_object_assign

    .align  2
    .code   16                      @ @__destroy_helper_block_
    .thumb_func     ___destroy_helper_block_
___destroy_helper_block_:
    ldr     r0, [r0, #20]
    movs    r1, #3
    b.w     __Block_object_dispose

我猜這些方法是在block拷貝和銷毀的時(shí)候調(diào)用,它們一定是在持有或釋放被block捕獲的對(duì)象。看起來拷貝函數(shù)用了兩個(gè)參數(shù),因?yàn)?code>r0和r1被尋址,它們兩可能有有效的數(shù)據(jù)。銷毀函數(shù)好像就一個(gè)參數(shù)。所有復(fù)雜的操作貌似都是_Block_object_assign_Block_object_dispose干的。這部分代碼在block runtime里。

如果你想了解更多關(guān)于block runtime的代碼,可以去http://compiler-rt.llvm.org下載源碼,重點(diǎn)看看runtime.c

下一篇我們將研究一下Block_Copy的原理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評(píng)論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評(píng)論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評(píng)論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,779評(píng)論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評(píng)論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評(píng)論 0 291
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評(píng)論 1 338
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,515評(píng)論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,750評(píng)論 1 375
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,933評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評(píng)論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,492評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,703評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容

  • 目錄 Block底層解析什么是block?block編譯轉(zhuǎn)換結(jié)構(gòu)block實(shí)際結(jié)構(gòu)block的類型NSConcre...
    tripleCC閱讀 33,249評(píng)論 32 388
  • 前言 Blocks是C語言的擴(kuò)充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,784評(píng)論 0 23
  • 1: 什么是block?1.0: Block的語法1.1: block編譯轉(zhuǎn)換結(jié)構(gòu)1.2: block實(shí)際結(jié)構(gòu) 2...
    iYeso閱讀 853評(píng)論 0 5
  • 原文地址:Objective-C中的Block 1.相關(guān)概念 在這篇筆記開始之前,我們需要對(duì)以下概念有所了解。 1...
    默默_David閱讀 415評(píng)論 0 1
  • 《Objective-C高級(jí)編程》這本書就講了三個(gè)東西:自動(dòng)引用計(jì)數(shù)、block、GCD,偏向于從原理上對(duì)這些內(nèi)容...
    WeiHing閱讀 9,870評(píng)論 10 69