美文网首页Block
iOS Block Part6:block拷贝的实现

iOS Block Part6:block拷贝的实现

作者: 破弓 | 来源:发表于2017-08-20 11:09 被阅读49次

    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

    相关文章

      网友评论

      本文标题:iOS Block Part6:block拷贝的实现

      本文链接:https://www.haomeiwen.com/subject/itpgkxtx.html