(譯)窺探Blocks(3) Block_copy

第一篇文章第二篇文章我們已經(jīng)研究了一些blocks的內(nèi)部原理了忆矛。本文將進一步研究block拷貝的過程。你可能聽到過一些術(shù)語比如"blocks 起始于棧"以及"如果想保存它們以后用你必須拷貝"魄梯。但是為什么呢?拷貝到底做了什么事奶赠?我長久以來一直在好奇拷貝block的機制到底是什么雳殊。比如block捕獲的值會怎么樣。本文我將對此做些闡述臀晃。

我們已經(jīng)知道的事

第一篇文章第二篇文章中我們知道一個block的內(nèi)存布局長這樣:

block內(nèi)存布局

第二篇文章中我們看到block最開始被引用的時候是在棧上創(chuàng)建的觉渴。既然是在棧上,那么在block的封閉域結(jié)束后內(nèi)存就會被回收重用积仗。那你之后再用這個block會發(fā)生什么呢?好吧蜕猫,你必須拷貝它寂曹。這是通過調(diào)用Block_copy()方法或者直接向他發(fā)送OC的copy消息完成。這就是所謂的Block_copy()回右。

Block_copy()

首先我們來看Block.h隆圆。其中有下面的定義:

#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))

void *_Block_copy(const void *arg);

所以Block_copy是一個宏,它將傳入的參數(shù)轉(zhuǎn)換為一個const void *然后傳遞給_Block_copy()方法翔烁。_Block_copy()的實現(xiàn)在runtime.c

void *_Block_copy(const void *arg) {
    return _Block_copy_internal(arg, WANTS_ONE);
}

所以也就是調(diào)用_Block_copy_internal方法渺氧,傳入block自己和WANTS_ONE。為了明白這什么意思蹬屹,我們需要看一下實現(xiàn)代碼侣背。也在runtime.c白华。下面是方法的實現(xiàn),已經(jīng)刪掉不想干的部分(主要是垃圾收集的部分):

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;

    // 1
    if (!arg) return NULL;

    // 2
    aBlock = (struct Block_layout *)arg;

    // 3
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }

    // 4
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }

    // 5
    struct Block_layout *result = malloc(aBlock->descriptor->size);
    if (!result) return (void *)0;

    // 6
    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first

    // 7
    result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
    result->flags |= BLOCK_NEEDS_FREE | 1;

    // 8
    result->isa = _NSConcreteMallocBlock;

    // 9
    if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
        (*aBlock->descriptor->copy)(result, aBlock); // do fixup
    }

    return result;
}

主要做了以下工作:

  1. 如果傳入?yún)?shù)是NULL就直接返回NULL贩耐。防止傳入一個NULL的Block弧腥。

  2. 將參數(shù)轉(zhuǎn)換為一個struct Block_layout類型的指針。你也許還記得第一篇文章中提到它潮太。它就是block內(nèi)部一個包含了實現(xiàn)函數(shù)和一些元數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)管搪。

  3. 如果block的flags字段包含BLOCK_NEEDS_FREE,那么這是一個堆block(稍后你就明白)铡买。這里只需要增加引用計數(shù)然后返回原blcok更鲁。

  4. 如果這是一個全局block(回看第一篇文章),那么不需要做任何事奇钞,直接返回原block澡为。因為全局block是一個單例。

  5. 如果走到這里蛇券,那么這一定是一個棧上分配的block缀壤。那樣的話,block需要拷貝到堆上纠亚。這才是有趣的部分塘慕。第一步,調(diào)用malloc()創(chuàng)建一塊特定的內(nèi)存蒂胞。如果創(chuàng)建失敗图呢,返回NULL;否則骗随,繼續(xù)蛤织。

  6. 調(diào)用memmove()方法將當前棧上分配的block按位拷貝到我們剛剛創(chuàng)建的堆內(nèi)存上。這樣可以保證所有的元數(shù)據(jù)都拷貝過來鸿染,比如descriptor指蚜。

  7. 接下來,更新標志位涨椒。第一行確保引用計數(shù)為0摊鸡。注釋表明這行其實不需要——大概這個時候引用計數(shù)已經(jīng)是0了。我猜保留這行是因為以前有個bug導致這里的引用計數(shù)不是0(所以說runtime的代碼也會偷懶)蚕冬。下一行設(shè)置了BLOCK_NEEDS_FREE標志位免猾,表明這是一個堆block,一旦引用計數(shù)減為0囤热,它所占用的內(nèi)存將被釋放猎提。|1操作設(shè)置block的引用計數(shù)為1。

  8. block的isa指針被設(shè)置為_NSConcreteMallocBlock旁蔼,說明這是個堆block锨苏。

  9. 最后疙教,如果block有一個拷貝輔助函數(shù),那么它將被調(diào)用蚓炬。必要的時候編譯器會生成拷貝輔助函數(shù)松逊。比如一個捕獲了對象的block就需要。那么拷貝輔助函數(shù)將持有被捕獲的對象肯夏。

哈哈经宏,已經(jīng)十分清晰了。現(xiàn)在你知道拷貝一個block到底發(fā)生了什么事驯击!但那只是圖片展示的一半內(nèi)容烁兰,對吧?釋放一個block又會怎么樣呢徊都?

Block_release()

Block_copy()圖的另一半是Block_release()沪斟。實際上這又是一個宏:

#define Block_release(...) _Block_release((const void *)(__VA_ARGS__))

Block_copy()一樣,Block_release()也是轉(zhuǎn)換傳入的參數(shù)然后調(diào)用一個方法暇矫。這一定程度上解放了程序員的雙手主之,他們不用自己做轉(zhuǎn)換。

我們來看看_Block_release()的源碼(簡明起見李根,重新整理了代碼順序槽奕,并刪除了垃圾回收相關(guān)的代碼):

    // 1
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if (!aBlock) return;

    // 2
    int32_t newCount;
    newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;

    // 3
    if (newCount > 0) return;

    // 4
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);
        _Block_deallocator(aBlock);
    }

    // 5
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        ;
    }

    // 6
    else {
        printf("Block_release called upon a stack Block: %p, ignored\n", (void *)aBlock);
    }
}

這段代碼做了這些事:

  1. 首先,參數(shù)被轉(zhuǎn)換為一個指向struct Block_layout的指針房轿。如果傳入NULL粤攒,直接返回。

  2. 標志位部分表示引用計數(shù)減1(之前Block_copy()中標志位操作代表的是引用計數(shù)置為1)囱持。

  3. 如果新的引用計數(shù)值大于0夯接,說明有其他東西在引用block,所以block不應該被釋放纷妆。

  4. 否則盔几,如果標志位包含BLOCK_NEEDS_FREE,那么這是一個堆block而且引用計數(shù)為0掩幢,應該被釋放逊拍。首先block的處理輔助函數(shù)(dispose helper)被調(diào)用,它是拷貝輔助函數(shù)(copy helper)的反義詞粒蜈,執(zhí)行相反的操作顺献,比如釋放被捕獲的對象旗国。最后調(diào)用_Block_deallocator方法釋放block枯怖。如果你查找runtime.c你就會發(fā)現(xiàn)這個方法最后就是一個free的函數(shù)指針,釋放malloc分配的內(nèi)存能曾。

  5. 如果到這一步且lock是全局的度硝,什么也不做肿轨。

  6. 如果到這一步,一定是發(fā)生了未知狀況蕊程,因為一個棧block試圖在這里釋放椒袍,輸出一行警告。實際上藻茂,你應該永遠不會走到這一步驹暑。

這些就是Block! 東西也并不多嘛(呵呵)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辨赐,一起剝皮案震驚了整個濱河市优俘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌掀序,老刑警劉巖帆焕,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異不恭,居然都是意外死亡叶雹,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進店門换吧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來折晦,“玉大人,你說我怎么就攤上這事式散〗钤猓” “怎么了?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵暴拄,是天一觀的道長漓滔。 經(jīng)常有香客問我,道長乖篷,這世上最難降的妖魔是什么响驴? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮撕蔼,結(jié)果婚禮上豁鲤,老公的妹妹穿的比我還像新娘。我一直安慰自己鲸沮,他們只是感情好琳骡,可當我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著讼溺,像睡著了一般楣号。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天炫狱,我揣著相機與錄音藻懒,去河邊找鬼。 笑死视译,一個胖子當著我的面吹牛嬉荆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播酷含,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鄙早,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了椅亚?” 一聲冷哼從身側(cè)響起蝶锋,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎什往,沒想到半個月后扳缕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡别威,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年躯舔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片省古。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡粥庄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出豺妓,到底是詐尸還是另有隱情惜互,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布琳拭,位于F島的核電站训堆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏白嘁。R本人自食惡果不足惜坑鱼,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望絮缅。 院中可真熱鬧鲁沥,春花似錦、人聲如沸耕魄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吸奴。三九已至允扇,卻和暖如春马靠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蔼两。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留逞度,地道東北人额划。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像档泽,于是被迫代替她去往敵國和親俊戳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,974評論 2 355

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

  • 《Objective-C高級編程》這本書就講了三個東西:自動引用計數(shù)馆匿、block抑胎、GCD,偏向于從原理上對這些內(nèi)容...
    WeiHing閱讀 9,816評論 10 69
  • 目錄 Block底層解析什么是block渐北?block編譯轉(zhuǎn)換結(jié)構(gòu)block實際結(jié)構(gòu)block的類型NSConcre...
    tripleCC閱讀 33,189評論 32 388
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,146評論 30 470
  • 本文為轉(zhuǎn)載: 作者:zyydeveloper 鏈接:http://www.reibang.com/p/5f776a...
    Buddha_like閱讀 876評論 0 2
  • 307阿逃、setValue:forKey和setObject:forKey的區(qū)別是什么? 答:1, setObjec...
    AlanGe閱讀 1,547評論 0 1