iOS Block Part6:block拷貝的實現

1.前導

1.1參考文檔

參考文檔1:BlocksRuntime/runtime.c
參考文檔2:Block_private.h

1.2帶入問題

Q1:棧block拷貝生成堆block的具體流程是怎樣的?
Q2:為什么棧block拷貝生成堆block,棧block捕獲的變量的__forwarding會指向堆上的變量?


block(int any)
struct __main_block_impl_0需要拷貝 

block(NSString * any)
struct __main_block_impl_0需要拷貝 
NSString * any需要拷貝

block(__block int any)
struct __main_block_impl_0需要拷貝 
struct __Block_byref_any_0需要拷貝

block(__block NSString * any)
struct __main_block_impl_0需要拷貝 
struct __Block_byref_any_0需要拷貝
NSString * any需要拷貝

四小類block的編譯結果都有struct __main_block_impl_0需要拷貝.struct __main_block_impl_0的拷貝也就是block的拷貝的起點.

2. block的拷貝的起點

NSObject.mm內的objc_retainBlock在block進行賦值(賦__strong變量,__weak變量不適用)的時候就會調用.這也真是block拷貝的開始.

//
// The -fobjc-arc flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block}
//

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

刨根問底會看到_Block_copy_internal方法,忽略所有的GC代碼,來個精簡版本:

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

    //printf("_Block_copy_internal(%p, %x)\n", arg, flags);
    if (!arg) return NULL;

    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }

    // Its a stack block.  Make a copy.
    if (!isGC) {
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return (void *)0;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 1;
        result->isa = _NSConcreteMallocBlock;
        if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
            //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
            (*aBlock->descriptor->copy)(result, aBlock); // do fixup
        }
        return result;
    }
}

功能很清晰,ARC環境下棧block自動拷貝成堆block會走:

// Its a stack block.  Make a copy.
if (!isGC) {
    struct Block_layout *result = malloc(aBlock->descriptor->size);
    if (!result) return (void *)0;
    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
    // reset refcount
    result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
    result->flags |= BLOCK_NEEDS_FREE | 1;
    result->isa = _NSConcreteMallocBlock;
    if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
        //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
        (*aBlock->descriptor->copy)(result, aBlock); // do fixup
    }
    return result;
}

以上代碼很好的回答了Q1:棧block拷貝生成堆block的具體流程是怎樣的?.

_Block_copy_internal內其他部分的代碼會和block的內存管理相關,下一篇文章會說.

3. block的拷貝的繼續

3.1 拷貝繼續,所用方法梳理

// Its a stack block.  Make a copy.
if (!isGC) {
    struct Block_layout *result = malloc(aBlock->descriptor->size);
    if (!result) return (void *)0;
    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
    // reset refcount
    result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
    result->flags |= BLOCK_NEEDS_FREE | 1;
    result->isa = _NSConcreteMallocBlock;
    if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
        //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
        (*aBlock->descriptor->copy)(result, aBlock); // do fixup
    }
    return result;
}
注意代碼:

if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
    //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
    (*aBlock->descriptor->copy)(result, aBlock); // do fixup
}

BLOCK_HAS_COPY_DISPOSE是標記struct __main_block_impl_0是否需要針對內部結構再深入拷貝,
如果需要繼續調用結構體的拷貝函數進行拷貝.
----------------------------------------------------------------------------------
block(NSString * any)

 NSString * any = ((NSString *(*)(id, SEL, NSString *, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_qp_p2pj3jmj65n39jgl4wx9_l9w0000gn_T_main_18d483_mi_0);
    
 void (*test)() = (
                   (void (*)())&__main_block_impl_0(
                                                    (void *)__main_block_func_0,
                                                    &__main_block_desc_0_DATA,
                                                    any,
                                                    570425344
                                                    )
                   );
----------------------------------------------------------------------------------
注意570425344.
enum {
    BLOCK_REFCOUNT_MASK =     (0xffff),
    BLOCK_NEEDS_FREE =        (1 << 24),
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25),
    BLOCK_HAS_CTOR =          (1 << 26), /* Helpers have C++ code. */
    BLOCK_IS_GC =             (1 << 27),
    BLOCK_IS_GLOBAL =         (1 << 28),
    BLOCK_HAS_DESCRIPTOR =    (1 << 29)
};
570425344為(1 << 25 | 1 << 29),所以570425344==>BLOCK_HAS_COPY_DISPOSE|BLOCK_HAS_DESCRIPTOR

以上展示的是block(NSString * any)傳入的flag.下面直接給出四類Block的結果,有興趣可以回頭看看編譯的源碼.

block(int any)==>flag=0==>完結
block(NSString * any)==>flag=570425344==>繼續內層拷貝
block(__block int any)==>flag=570425344==>繼續內層拷貝
block(__block NSString * any)==>flag=570425344==>繼續內層拷貝

可以看出除捕獲int any的block在經過一層拷貝之后完結外,其他的都仍然要繼續.

block(int any)
struct __main_block_impl_0需要拷貝 所用方法==>static void _Block_copy_internal ==> 分析完畢
------完結-----

-------------------------------------------------------------

block(NSString * any)
struct __main_block_impl_0需要拷貝 所用方法==>static void _Block_copy_internal ==> 分析完畢
NSString * any需要拷貝==>繼續

block(__block int any)
struct __main_block_impl_0需要拷貝 所用方法==>static void _Block_copy_internal ==> 分析完畢
struct __Block_byref_any_0需要拷貝==>繼續 

block(__block NSString * any)
struct __main_block_impl_0需要拷貝 所用方法==>static void _Block_copy_internal ==> 分析完畢
struct __Block_byref_any_0需要拷貝==>繼續 
NSString * any需要拷貝 

(*aBlock->descriptor->copy)指向的方法前面的文章已經有過代碼連線+示意圖,這里就不再說明了.

  • block捕獲對象類型,編譯結果中有的對對象類型的拷貝方法
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->any, (void*)src->any, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
  • block捕獲__block修飾的數據,編譯結果中有的對struct __Block_byref_any_0的拷貝方法
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->any, (void*)src->any, 8/*BLOCK_FIELD_IS_BYREF*/);
}
  • block捕獲__block修飾的對象,編譯結果中有的對struct __Block_byref_any_0內部對象的拷貝方法
static void __Block_byref_id_object_copy_131(void *dst, void *src)
{
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

__block NSString * any編譯后的結構體 __Block_byref_any_0會比
__block int any編譯后的結構體 __Block_byref_any_0多出兩個函數指針

struct __Block_byref_any_0 {
void *__isa;
__Block_byref_any_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);//多出的
 void (*__Block_byref_id_object_dispose)(void*);//多出的
 NSString *any;
};

__Block_byref_id_object_copy__Block_byref_id_object_copy_131對應

block(NSString * any)
struct __main_block_impl_0需要拷貝 所用方法==>static void _Block_copy_internal ==> 分析完畢
NSString * any需要拷貝 所用方法==>static void __main_block_copy_0

block(__block int any)
struct __main_block_impl_0需要拷貝 所用方法==>static void _Block_copy_internal ==> 分析完畢
struct __Block_byref_any_0需要拷貝 所用方法==>static void __main_block_copy_0

block(__block NSString * any)
struct __main_block_impl_0需要拷貝 所用方法==>static void _Block_copy_internal ==> 分析完畢
struct __Block_byref_any_0需要拷貝 所用方法==>static void __main_block_copy_0
NSString * any需要拷貝 所用方法==>static void __Block_byref_id_object_copy_131

3.2 所用方法內部是什么

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->any, (void*)src->any, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->any, (void*)src->any, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __Block_byref_id_object_copy_131(void *dst, void *src)
{
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

他們里面調用的都調用了_Block_object_assign.

各位注意:
_Block_object_assign非常非常非常重要,可以說block捕獲參數的拷貝就是在_Block_object_assign函數里"繞"!!!

3.3 _Block_object_assign

/*
 * When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
 * to do the assignment.
 */
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
    //printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
    if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
        if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
            _Block_assign_weak(object, destAddr);
        }
        else {
            // do *not* retain or *copy* __block variables whatever they are
            _Block_assign((void *)object, destAddr);
        }
    }
    else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  {
        // copying a __block reference from the stack Block to the heap
        // flags will indicate if it holds a __weak reference and needs a special isa
        _Block_byref_assign_copy(destAddr, object, flags);
    }
    // (this test must be before next one)
    else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {
        // copying a Block declared variable from the stack Block to the heap
        _Block_assign(_Block_copy_internal(object, flags), destAddr);
    }
    // (this test must be after previous one)
    else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
        //printf("retaining object at %p\n", object);
        _Block_retain_object(object);
        //printf("done retaining object at %p\n", object);
        _Block_assign((void *)object, destAddr);
    }
}
_Block_object_assign參數flag相關

BLOCK_FIELD_IS_BLOCK (7)
BLOCK_FIELD_IS_BYREF (8)
BLOCK_FIELD_IS_CALLER (128)
BLOCK_FIELD_IS_OBJECT (3)
BLOCK_FIELD_IS_WEAK (16)

再看看先前相關總結的方法調用_Block_object_assign用的都是什么flag

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) 
{
_Block_object_assign((void*)&dst->any, (void*)src->any, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

3==BLOCK_FIELD_IS_OBJECT
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->any, (void*)src->any, 8/*BLOCK_FIELD_IS_BYREF*/);
}

8==BLOCK_FIELD_IS_BYREF
static void __Block_byref_id_object_copy_131(void *dst, void *src)
{
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

131==BLOCK_FIELD_IS_OBJECT||BLOCK_FIELD_IS_CALLER;

_Block_object_assign三個參數:
參數1:目標地址
參數2:源對象地址
參數3:flog的不同確定后方走什么路

_Block_object_assign與一系列其他函數的嵌套使用造就了block對捕獲參數的拷貝.

4._Block_object_assign內部流程

block(int)
block(NSString * any)
block(__block int)
block(__block NSString * any)

四小類block中后三種都會有對_Block_object_assign的調用,本文就不一一的說了,因為有很多重復的內容.
本文以block(__block NSString * any)的拷貝來說明
__block NSString * any = [NSString stringWithFormat:@"1"];
void (^test)() = ^ {
    NSLog(@"%@",any);
};
test();

4.1 調用__main_block_copy_0

調用方式:
__main_block_copy_0調用_Block_object_assign
實現:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->any, (void*)src->any, 8/*BLOCK_FIELD_IS_BYREF*/);
}
參數1:目標__main_block_impl_0->__Block_byref_any地址
參數2:源__main_block_impl_0->__Block_byref_any地址
參數3: BLOCK_FIELD_IS_BYREF

用途:
拷貝結構體__Block_byref_any(結構體內含NSString * any)

else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  {
// copying a __block reference from the stack Block to the heap
// flags will indicate if it holds a __weak reference and needs a special isa
_Block_byref_assign_copy(destAddr, object, flags);
}
_Block_byref_assign_copy內部實現(已經刪除GC相關內容),所走的代碼已經標注.

static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
    struct Block_byref **destp = (struct Block_byref **)dest;
    struct Block_byref *src = (struct Block_byref *)arg;
        
    //printf("_Block_byref_assign_copy called, byref destp %p, src %p, flags %x\n", destp, src, flags);
    //printf("src dump: %s\n", _Block_byref_dump(src));
    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {<<4.1入口
        //printf("making copy\n");
        // src points to stack
        bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
        // if its weak ask for an object (only matters under GC)
        struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
        copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
        copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;
        if (isWeak) {
            copy->isa = &_NSConcreteWeakBlockVariable;  // mark isa field so it gets weak scanning
        }
        if (src->flags & BLOCK_HAS_COPY_DISPOSE) {<<4.2入口
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            copy->byref_keep = src->byref_keep;
            copy->byref_destroy = src->byref_destroy;
            (*src->byref_keep)(copy, src);
        }
        else {
            // just bits.  Blast 'em using _Block_memmove in case they're __strong
            _Block_memmove(
                (void *)&copy->byref_keep,
                (void *)&src->byref_keep,
                src->size - sizeof(struct Block_byref_header));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    // assign byref data block pointer into new Block
    _Block_assign(src->forwarding, (void **)destp);
}

Q2解決:為什么棧block拷貝生成堆block,棧block捕獲的變量的__forwarding會指向堆上的變量?下面的代碼片段可以很好的解釋.

struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
src->forwarding = copy; // patch stack to point to heap copycopy->size = src->size;

到此為為止:

block(__block NSString * any)
struct __main_block_impl_0需要拷貝 所用方法==>static void _Block_copy_internal ==> 分析完畢
struct __Block_byref_any_0需要拷貝 所用方法==>static void __main_block_copy_0 ==> 分析完畢
NSString * any需要拷貝 所用方法==>static void __Block_byref_id_object_copy_131

4.2 調用__Block_byref_id_object_copy_131

4.2.1 如何調用到__Block_byref_id_object_copy_131
if (src->flags & BLOCK_HAS_COPY_DISPOSE) {<<4.2入口
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
copy->byref_keep = src->byref_keep;
copy->byref_destroy = src->byref_destroy;
(*src->byref_keep)(copy, src);
}

調用src(_Block_byref結構體)byref_keep函數
_Block_byref結構體又哪來的byref_keep函數?
Block_private.h 與C++編譯的對比

Block_private.h內

struct Block_byref {
    void *isa;
struct Block_byref *forwarding;
    int flags; /* refcount; */
int size;
    void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
void (*byref_destroy)(struct Block_byref *);
/* long shared[0]; */
};
C++編譯內

struct __Block_byref_any_0 {
 void *__isa;
 __Block_byref_any_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSString *any;
};

顯而易見__Block_byref_id_object_copy就是byref_keep.

__Block_byref_id_object_copy又指向誰呢?

當然是靜態函數__Block_byref_id_object_copy_131

4.2.2 __Block_byref_id_object_copy_131具體分析

調用方式:
__Block_byref_id_object_copy_131調用_Block_object_assign

實現:

static void __Block_byref_id_object_copy_131(void *dst, void *src)
{
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
關于(char*)dst + 40,(char*)src + 40的解釋

struct __Block_byref_any_0 {
void *__isa;
__Block_byref_any_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);//多出的
 void (*__Block_byref_id_object_dispose)(void*);//多出的
 NSString *any;
};
4個地址的字節數(32)+2個int的字節數(8)=40字節,所以加40之后剛好指向NSString *any;
參數1:目標__main_block_impl_0->__Block_byref_any->NSString * any
參數2:源__main_block_impl_0->__Block_byref_any->NSString * any
參數3: BLOCK_FIELD_IS_OBJECT||BLOCK_FIELD_IS_CALLER

用途:
拷貝__Block_byref_any內含的NSString * any(內部層次關系:__main_block_impl_0->__Block_byref_any->NSString * any)

if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
_Block_assign_weak(object, destAddr);
} else {
// do *not* retain or *copy* __block variables whatever they are
_Block_assign((void *)object, destAddr);
}
}

到此為止,捕獲__block修飾對象數據類型block的拷貝3層,全部梳理完成.讓我們再來看看示意圖.


block(__block NSString)_map.png
block(__block NSString * any)
struct __main_block_impl_0需要拷貝 所用方法==>static void _Block_copy_internal ==> 分析完畢
struct __Block_byref_any_0需要拷貝 所用方法==>static void __main_block_copy_0 ==> 分析完畢
NSString * any需要拷貝 所用方法==>static void __Block_byref_id_object_copy_131 ==> 分析完畢

在分析block(__block NSString * any)的三層拷貝的過程中,我們也已經對最開始提出的問題進行回答.
其他小類block拷貝的步驟是block(__block NSString * any)的子集,有興趣可以自己梳理流程.
另外銷毀與拷貝是互逆的過程,所有的銷毀方法也在BlocksRuntime/runtime.c內有興趣,可以再看看源碼.

  • what's more!

block拷貝其實也是block內存管理的一部分,下一篇將展開講講block的內存管理.


參考文獻:
Block技巧與底層解析 by tripleCC

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 前言 Blocks是C語言的擴充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,785評論 0 23
  • 《Objective-C高級編程》這本書就講了三個東西:自動引用計數、block、GCD,偏向于從原理上對這些內容...
    WeiHing閱讀 9,898評論 10 69
  • Block是iOS開發中一種比較特殊的數據結構,它可以保存一段代碼,在合適的地方再調用,具有語法簡介、回調方便、編...
    飛魚灣閱讀 4,159評論 0 7
  • 1: 什么是block?1.0: Block的語法1.1: block編譯轉換結構1.2: block實際結構 2...
    iYeso閱讀 873評論 0 5
  • 目錄 Block底層解析什么是block?block編譯轉換結構block實際結構block的類型NSConcre...
    tripleCC閱讀 33,268評論 32 388