第三十六節(jié)—Block(三)

本文為L_Ares個人寫作溺健,以任何形式轉(zhuǎn)載請表明原文出處。

準備

1. libclosure源碼

libclosure-73源碼文件

2. block的.cpp文件

clang獲取存在block.cpp文件的方法在上一篇文中有專門的教程阎抒。

一、關(guān)于全局Block

前兩節(jié)中,我們已經(jīng)了解了常用的Block的3種常見的分類返敬,也知道了全局Block如果捕獲了外界變量的話,就會從全局Block變成棧Block寥院。

其實對于Block的類型判斷是由編譯器進行分辨的劲赠,在編譯期的時候 :

  1. 如果Block并未進行捕獲外界變量的操作,那么Block就會被認為是NSGlobalBlock秸谢。
  2. 對于帶有參數(shù)的Block凛澎,參數(shù)不屬于外界變量,屬于Block的內(nèi)部變量估蹄,所以類型也是NSGlobalBlock塑煎。
  3. Block對外界變量進行了捕獲之后,編譯器會對Block的類型判斷變成NSStackBlock臭蚁。
  4. Block是否是全局變量的判斷最铁,會存儲在Block的結(jié)構(gòu)體屬性flags中讯赏,在其第28位上。
  5. 編譯器不會對NSGlobalBlock主動做copy操作冷尉,即使開發(fā)者手動對NSGlobalBlock進行copy方法的調(diào)用待逞,NSGlobalBlock也不會發(fā)生類型的改變。

二网严、棧Block變成堆Block的源碼解析

在上面识樱,我們已經(jīng)知道了,全局Block也就是NSGlobalBlock和堆震束、棧的Block是的區(qū)分條件手段 :

編譯器的在編譯期就通過對Block是否捕獲外界變量進行區(qū)分怜庸。

而對于捕獲外界變量的棧和堆Block,在上一節(jié)的Block內(nèi)存變化中垢村,已經(jīng)通過匯編的分析割疾,知道了一個條件 :

在聲明Block的時候,會通過objc_retainBlock_Block_copyNSStackBlock變成NSMallocBlock嘉栓。

那么這里就會通過libclosure源碼探索一下_Block_copy的實現(xiàn)思路宏榕。

操作 :

在準備好的libclosure源碼中全局搜索_Block_copy,找到其在runtime.cpp文件中的函數(shù)實現(xiàn)侵佃。

結(jié)果 :

圖2.0.1.png

結(jié)論 :

對于Block的自身從棧區(qū)拷貝到堆區(qū) :

  1. Block塊的復(fù)制首先要把需要被復(fù)制的Block轉(zhuǎn)成Block的本質(zhì)結(jié)構(gòu)Block_layout結(jié)構(gòu)體麻昼。
  2. 然后判斷Block塊的類型屬性
  • 如果已經(jīng)是堆Block了,那么只利用自身的引用計數(shù)管理馋辈,改變Block的flags中的第2位BLOCK_REFCOUNT_MASK就可以抚芦。
  • Block的引用計數(shù)管理是自己進行管理,不實用runtime底層的引用計數(shù)方式迈螟。
  • 如果是全局Block叉抡,則不發(fā)生任何的拷貝,直接返回原Block答毫。
  • 如果是棧Block褥民,則在堆區(qū)申請一塊和原來棧Block內(nèi)存大小一樣的內(nèi)存,利用位拷貝洗搂,將棧Block塊整體的拷貝到堆區(qū)消返,保證堆Block和棧Block的數(shù)據(jù)完全一致。
  • 并且重置引用計數(shù)為1蚕脏,為了讓內(nèi)存工具可以看到完整正確的Block信息侦副,最后才將Block的isa指向NSMallocBlock類。

三驼鞭、Block捕獲的外界變量的copy

在上面秦驯,我們已經(jīng)知道了Block塊是如何從棧區(qū)拷貝到堆區(qū)的,在上面的圖2.0.1中挣棕,可以找到Block的isa译隘、flags亲桥、invoke都很明顯的進行了從棧區(qū)到堆區(qū)的拷貝,那么除了一個保留值reserved固耘,還有一個Block的描述并沒有明顯的進行拷貝而是調(diào)用了_Block_call_copy_helper题篷。

對于被Block捕獲的外界變量也需要隨Block一起拷貝到堆區(qū),才能保證外界變量的生命周期在Block內(nèi)部得以延長厅目,也就是說番枚,_Block_call_copy_helper的這一步就代表著要對已然存儲在棧Block上的外界變量copy到堆Block上。

問題 :

Block捕獲的外界變量是如何隨著棧區(qū)Block拷貝到堆區(qū)Block上的损敷?

已知條件 :

_Block_call_copy_helper的調(diào)用會讓Block捕獲的外界變量隨著棧區(qū)Block一起拷貝到堆區(qū)Block上葫笼。

操作 :

逐步進入_Block_call_copy_helper的實現(xiàn),找到和copy相關(guān)的線索拗馒。

結(jié)果 :


由已知條件可知思路 :

  1. 從Block的構(gòu)造函數(shù)找到descriptor2的賦值路星。
  2. descriptor2找到被Block捕獲的外界變量是如何隨著Block一起拷貝到堆上的。

進行探索 :

操作1 :

  1. 創(chuàng)建一個iOSProject诱桂。
  2. main.m文件中直接寫入以下代碼洋丐。
  3. 通過clangmain.m文件編譯成main.cpp文件,進度條拉到最后挥等,查看__blockc++實現(xiàn)友绝。
  4. 刪除掉main函數(shù)中無關(guān)的代碼,刪除強轉(zhuǎn)触菜,只保留Block相關(guān)的代碼九榔。
  • main.m代碼 :
#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        
        //這里不要直接用jd_name = @"JD"
        //要用[NSString stringWithFormat:@"JD"],否則clang未必能編譯成功
        __block NSString *jd_name = [NSString stringWithFormat:@"JD"];
        void(^jd_block)(void) = ^{
            jd_name = @"eason";
            NSLog(@"%@",jd_name);
        };
        jd_block();
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

  • clang命令涡相,這里注意,main.m的代碼中明顯的有<UIKit>框架的使用剩蟀,直接用以前的clang命令是無法編譯到<UIKit>框架的催蝗,所以clang指令會變成下面的 :
xcrun -sdk  iphonesimulator clang -rewrite-objc main.m

結(jié)果1 :

圖3.0.1.png

操作2 :

搜索Block構(gòu)造函數(shù)的第二個參數(shù)__main_block_desc_0_DATA

結(jié)果2 :

圖3.0.2.png

操作3 :

搜索__main_block_desc_0_DATA的屬性值__main_block_copy_0__main_block_dispose_0育特。

結(jié)果3 :

圖3.0.3.png

發(fā)現(xiàn)拷貝輔助函數(shù)丙号,也就是Block_descriptor_2存儲的是對__block修飾的外界變量進行assigndispose的函數(shù)——_Block_object_assign()_Block_object_dispose()

操作4 :

libclosure源碼中搜索_Block_object_assign缰冤。找到拷貝輔助函數(shù)的實現(xiàn)犬缨。

結(jié)果4 :

圖3.0.4.png

操作4.1 :

首先,看一下拷貝輔助函數(shù)的switch條件棉浸,也就是BLOCK_ALL_COPY_DISPOSE_FLAGS是什么怀薛。

結(jié)果4.1 :

enum {
    BLOCK_ALL_COPY_DISPOSE_FLAGS = 
        BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_BYREF |
        BLOCK_FIELD_IS_WEAK | BLOCK_BYREF_CALLER
};

enum {
    BLOCK_FIELD_IS_OBJECT   =  3,
    BLOCK_FIELD_IS_BLOCK    =  7,
    BLOCK_FIELD_IS_BYREF    =  8,
    BLOCK_FIELD_IS_WEAK     = 16,
    BLOCK_BYREF_CALLER      = 128
}

1.BLOCK_FIELD_IS_OBJECT = 3: 對象
2.BLOCK_FIELD_IS_BLOCK = 7: 普通變量
3.BLOCK_FIELD_IS_BYREF = 8: __block修飾的結(jié)構(gòu)體
4.BLOCK_FIELD_IS_WEAK = 16: __weak修飾的變量
5.BLOCK_BYREF_CALLER = 128: 處理Block_byref結(jié)構(gòu)體內(nèi)部對象的內(nèi)存的時候添加的額外標記,要配合上面的枚舉一起使用到

操作4.2 :

查看Block捕獲對象的時候迷郑,是如何進行拷貝的枝恋。也就是圖3.0.5中_Block_retain_object的實現(xiàn)创倔。

結(jié)果4.2 :

圖3.0.5.png

操作4.3 :

查看Block捕獲普通變量的時候,是如何進行拷貝的焚碌。也就是圖3.0.5中的_Block_copy的實現(xiàn)畦攘。

結(jié)果4.3 :

在上面的二、棧Block變成堆Block的源碼解析 中已經(jīng)介紹過了十电。

操作4.4 :

查看Block捕獲經(jīng)__block修飾的變量的時候知押,是如何進行拷貝的。

結(jié)果4.4 :

圖3.0.6

操作5 :

這里繼續(xù)對__block修飾的變量進行拷貝的實現(xiàn)的探索鹃骂。進入_Block_byref_copy的實現(xiàn)台盯。

結(jié)果5 :

圖3.0.7.png

Tips :

還記得上一節(jié)提到的,__block修飾的變量偎漫,在Block函數(shù)內(nèi)部可以進行修改的原因是指針的copy爷恳,而指針則必然指向變量的值所在的地址,更改的是這個地址上的值象踊。那么這個地址上的數(shù)據(jù)也要跟隨Block從椢虑祝拷貝到堆才對。

操作6 :

  1. 在匯編的.cpp文件中找到__Block_byref_jd_name_0結(jié)構(gòu)體杯矩。
  2. 然后在libclosure中找到Block_byref結(jié)構(gòu)體栈虚。

結(jié)果6 :

圖3.0.8.png

可以看到,Block捕獲的外界變量在匯編后被轉(zhuǎn)為的結(jié)構(gòu)體的本質(zhì)就是Block_byref結(jié)構(gòu)體史隆。它的結(jié)構(gòu)設(shè)計和Block塊的結(jié)構(gòu)設(shè)計是及其類似的魂务。

操作7 :

  1. 根據(jù)圖3.0.7和圖3.0.8,在圖3.0.7中泌射,發(fā)現(xiàn)了這樣一步調(diào)用(*src2->byref_keep)(copy, src);粘姜,在圖3.0.8中,可以知道byref_keep函數(shù)是__Block_byref_id_object_copy熔酷。
  2. .cpp中搜索__Block_byref_id_object_copy函數(shù)的實現(xiàn)孤紧。

結(jié)果7 :

圖3.0.9.png

操作8 :

  1. 可以看到,對Block_byref結(jié)構(gòu)體中的NSString *jd_namecopy還是利用圖3.0.4中的方法拒秘,并且這次走的是第一個case号显,也就是BLOCK_FIELD_IS_OBJECTcopy。原因很簡單躺酒,看畫紅框的+40押蚤,就是讓Block_byref結(jié)構(gòu)體的指針偏移40字節(jié)。而Block_byref結(jié)構(gòu)體指針偏移40字節(jié)就是NSString *jd_name;的地址羹应。
  2. 所以揽碘,這一步就完成了__block捕獲的外界變量的copy操作。

結(jié)果8 :

圖3.0.10.png

四、總結(jié)

  1. 全局Block和堆棧Block的區(qū)分是編譯器在編譯期做出的判斷
  • 沒有捕獲外界變量的Block和定義在全局區(qū)的Block都是全局Block钾菊。
  • 捕獲了外界變量的Block是棧Block帅矗。
  • 為了延長棧Block中的捕獲到的外界變量的生命周期,防止操作系統(tǒng)的自動釋放煞烫,所以將棧Block拷貝到堆Block浑此,并把棧Block的指針指向堆Block,這樣更穩(wěn)定滞详、更安全凛俱。
  1. 棧Block拷貝到堆Block是調(diào)用了_Block_copy函數(shù),將整個Block塊拷貝到堆上料饥。
  2. 堆Block擁有引用計數(shù)蒲犬,并且由Block的flags屬性進行記錄管理,不使用runtime底層的引用計數(shù)管理岸啡。
  3. 對于Block捕獲外界變量原叮,分為兩種情況,一種是沒有__block修飾的外界變量巡蘸。一種是有__block修飾的外界變量奋隶。無論是否有__block的修飾,它們的共同點都是可能會利用存儲在descriptor2中的拷貝輔助函數(shù)悦荒,將存儲在棧Block上的外界變量拷貝到堆Block上唯欣。
  • 4.1 對于沒有__block修飾的外界變量
    • 就當作是對象進行拷貝,外界變量的引用計數(shù)依然是runtime進行管理搬味。
    • 調(diào)用的是_Block_object_assign函數(shù)境氢。
  • 4.2 對于擁有__block修飾的外界變量
    • 需要兩次復(fù)制,先進行外界變量的結(jié)構(gòu)體的copy碰纬,也就是Block_byref對象的拷貝萍聊。
    • 再利用Block_byref結(jié)構(gòu)體中的拷貝輔助函數(shù),對外界變量結(jié)構(gòu)體中真正的外界變量進行copy悦析。
    • 調(diào)用的也是_Block_object_assign函數(shù)脐区。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市她按,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌炕柔,老刑警劉巖酌泰,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異匕累,居然都是意外死亡陵刹,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門欢嘿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來衰琐,“玉大人也糊,你說我怎么就攤上這事∠壑妫” “怎么了狸剃?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長狗热。 經(jīng)常有香客問我钞馁,道長,這世上最難降的妖魔是什么匿刮? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任僧凰,我火速辦了婚禮,結(jié)果婚禮上熟丸,老公的妹妹穿的比我還像新娘训措。我一直安慰自己,他們只是感情好光羞,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布绩鸣。 她就那樣靜靜地躺著,像睡著了一般狞山。 火紅的嫁衣襯著肌膚如雪全闷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天萍启,我揣著相機與錄音总珠,去河邊找鬼。 笑死勘纯,一個胖子當著我的面吹牛局服,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播驳遵,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼淫奔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了堤结?” 一聲冷哼從身側(cè)響起唆迁,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎竞穷,沒想到半個月后唐责,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡瘾带,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年鼠哥,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡朴恳,死狀恐怖抄罕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情于颖,我是刑警寧澤呆贿,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站恍飘,受9級特大地震影響榨崩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜章母,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一母蛛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乳怎,春花似錦彩郊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至询枚,卻和暖如春违帆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背金蜀。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工刷后, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人渊抄。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓尝胆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親护桦。 傳聞我的和親對象是個殘疾皇子含衔,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

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