Block

作者: MaZengyi | 来源:发表于2017-01-08 21:56 被阅读33次

    Block 是 iOS4 之后添加的一种语法结构,也成为闭包,或者匿名函数。在 iOS 中被广泛的使用,著名的第三方库也大量用到此特性,如 AFNetworking,SDWebimage 等。

    下文将介绍几个方面

    1. Block 的基础用法
    2. Block 底层表示
    3. Block 的变量捕获
    4. __block 变量底层描述
    5. OC 中的 Block 3 种类型
    6. Block 的 copy 相关的理解
    7. __block 的 __forwarding

    Block 语法

    当做局部变量

    returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
    

    当做属性

    @property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes);
    

    当做方法参数

    - (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;
    

    调用方法

    [someObject someMethodThatTakesABlock:^returnType (parameters) {...}];
    

    typedef

    typedef returnType (^TypeName)(parameterTypes);
    TypeName blockName = ^returnType(parameters) {...};
    

    Block 底层

    我们可以使用 clang 的 rewrite 指令来生成 C/C++ 描述来供我们研究 Block 底层实现原理,首先先写一个最简单的 Block

    int main(int argc, const char * argv[]) {
        void(^block)() = ^()
        {
            NSLog(@"hello");
        };
        block();
        return 0;
    }
    

    转换后的代码

    
    
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_v4_8fn73k9s2p5bxbsdm0cvqyd40000gn_T_main_3d0d97_mi_0);
        }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    
    int main(int argc, const char * argv[]) {
        void(*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        return 0;
    }
    

    整理下,里面有几个结构体,第一个 __main_block_func_0

    这个比较简单,从代码可以看出是我们 block 里面执行语句,一个执行函数,参数是 __cself 类型是 __main_block_impl_0 也就是我们 block 本身,下文会提到。

    第二个是__main_block_desc_0 一个 block 的描述结构体,其中有2个字段:

    • reserved:保留字段。
    • Block_size block 大小。

    第三个是__block_impl,这个是 block 对象。其中哟几个字段

    • isa:类似于类的 isa 的指针,模拟对象。在 ARC 下有3种类型分别是_NSConcreteStackBlock_NSConcreteMallocBlock_NSConcreteGlobalBlock
    • Flags:标志位,有以下几个
    enum {
        BLOCK_DEALLOCATING =      (0x0001),  // runtime
        BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
        BLOCK_NEEDS_FREE =        (1 << 24), // runtime
        BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
        BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
        BLOCK_IS_GC =             (1 << 27), // runtime
        BLOCK_IS_GLOBAL =         (1 << 28), // compiler
        BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
        BLOCK_HAS_SIGNATURE  =    (1 << 30)  // compiler
    };
    
    • Reserved:保留位
    • FuncPtr:block 执行的函数指针地址。

    看完以上3个结构在来看这个__main_block_impl_0就比较简单了。此结构体包含了一个__block_impl__main_block_desc_0结构体,
    还有一个初始化方法,代码比较简单,只是赋值相应的字段。
    终于进入到我们的 main函数了

    void(*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    

    首先声明了一个函数指针 block 也就是我们 OC 代码中的

    void(^block)()
    

    然而右边转换成了

    ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    

    这句话的意思是,让 block 这个变量的指针指向新建生成的__main_block_impl_0结构体变量。它调用的构造器并且传入 __main_block_func_0(函数地址),__main_block_desc_0_DATA(描述字段)

    下一句也就是我们执行的 OC 代码

        block();
    

    被转换成

        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    

    这里比较难理解,我们忽略多余的类型转换这里取出了 block 指针的第一个成员(也就是__main_block_impl_0中的impl,因为指针类型是__block_impl,也就是在这个地址连续取__block_impl的大小,又因为impl是第一个结构体成员,所以取出impl),之后调用了impl.FuncPtrblock 实现函数指针,传入参数 block,也就是自己。

    这里我们上文提到

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_v4_8fn73k9s2p5bxbsdm0cvqyd40000gn_T_main_3d0d97_mi_0);
        }
    

    这里有个参数是__cself,类型也是__main_block_impl_0,也就是传入 block 自己本身的结构体,这不难理解,联想面向对象语言中的方法第一个参数都会是self,这也是 block 模拟对象的原理之一,当然还有更大用处,这里后文提到。

    有一个点需要注意一下, block 结构体有一定的命名规律(__xxx_block_impl_y:这里的 xxx 是 block 名称,y 是该函数出现的顺序值,如果 block 是匿名的则会是当前作用域的函数名)。

    变量捕获

    我们一直都知道,在 Block 中基础类型的变量会被拷贝值,而指针变量则会捕获指针变量,并且强引用,那么这到底是怎么回事呢?我们一起来研究一下
    写一个捕获外部变量的 block

    int main(int argc, const char * argv[]) {
        int i = 0;
        NSMutableArray *n = [NSMutableArray array];
        void(^block)() = ^()
        {
            int a = i;
            [n addObject:@"1"];
            NSLog(@"hello");
        };
        block();
        return 0;
    }
    

    改写之后变成如下

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int i;
      NSMutableArray *n;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, NSMutableArray *_n, int flags=0) : i(_i), n(_n) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int i = __cself->i; // bound by copy
      NSMutableArray *n = __cself->n; // bound by copy
    
            int a = i;
            ((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)n, sel_registerName("addObject:"), (id)(NSString *)&__NSConstantStringImpl__var_folders_v4_8fn73k9s2p5bxbsdm0cvqyd40000gn_T_main_16958d_mi_0);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_v4_8fn73k9s2p5bxbsdm0cvqyd40000gn_T_main_16958d_mi_1);
        }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->n, (void*)src->n, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->n, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    int main(int argc, const char * argv[]) {
    
    
        int i = 0;
        NSMutableArray *n = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
        void(*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i, n, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        return 0;
    }
    

    我们看到__main_block_impl_0多了2个变量,一个是 i 一个是 指针 n,这和我们捕获的变量是一致的,构造函数中初始化这 2 个值,也就是在底层,对于基础类型,内部会维护一个相应的基础类型变量,对于对象,则内部也会有指向这个对象的指针(默认为强引用)。
    对于代码中出现的 copy 和 dispose 下文我们会提到,可以先忽略它,

    __block 底层描述

    如果想在 block 中改变一个外界变量的值,那么这个变量必须声明为 __block,这在底层是怎么实现的呢,我们一起来从代码说话。
    写一个__block的例子

    int main(int argc, const char * argv[]) {
        __block int i = 0;
       __block NSMutableArray *n = [NSMutableArray array];
        void(^block)() = ^()
        {
            i = 1;
            n = [NSArray array];
            NSLog(@"hello");
        };
        block();
        return 0;
    }
    

    改写后的代码

    struct __Block_byref_i_0 {
      void *__isa;
    __Block_byref_i_0 *__forwarding;
     int __flags;
     int __size;
     int i;
    };
    struct __Block_byref_n_1 {
      void *__isa;
    __Block_byref_n_1 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     NSMutableArray *n;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_i_0 *i; // by ref
      __Block_byref_n_1 *n; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, __Block_byref_n_1 *_n, int flags=0) : i(_i->__forwarding), n(_n->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_i_0 *i = __cself->i; // bound by ref
      __Block_byref_n_1 *n = __cself->n; // bound by ref
    
            (i->__forwarding->i) = 1;
            (n->__forwarding->n) = ((NSArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("array"));
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_v4_8fn73k9s2p5bxbsdm0cvqyd40000gn_T_main_ea2303_mi_0);
        }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->n, (void*)src->n, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->n, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    int main(int argc, const char * argv[]) {
    
        __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};
       __attribute__((__blocks__(byref))) __Block_byref_n_1 n = {(void*)0,(__Block_byref_n_1 *)&n, 33554432, sizeof(__Block_byref_n_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"))};
        void(*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, (__Block_byref_n_1 *)&n, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        return 0;
    }
    

    我们注意到多了 2 个结构体分别是__Block_byref_i_0__Block_byref_n_1。i 为基础类型,n 为对象类型,
    在底层__block修饰的变量会被转换为__Block_byref结构体,
    其中分别有

    • __isa:isa 指针,模拟对象特性
    • __forwarding:转发对象,后文会提到。
    • __flags:标志位
    • __size:大小
    • 如果是对象类型,会多出__Block_byref_id_object_copy__Block_byref_id_object_dispose 这 2 都是和 copy 相关的操作,
    • 和变量捕获一样,会维护一个内部变量。

    于捕获不同__main_block_impl_0这时候维护的变量会变成__Block_byref结构体变量(__Block_byref 内部也维护了一个变量)。构造函数在构造的时候会把 __forwarding 会指向外界的 __block对象
    在来看执行函数的部分代码

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_i_0 *i = __cself->i; // bound by ref
      __Block_byref_n_1 *n = __cself->n; // bound by ref
    
            (i->__forwarding->i) = 1;
            (n->__forwarding->n) = ((NSArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("array"));
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_v4_8fn73k9s2p5bxbsdm0cvqyd40000gn_T_main_ea2303_mi_0);
        }
    

    可以看出,执行部分也变了,直接将__forwarding里面所维护的变量拿出来设置值,从而达到在 block 体内修改变量的作用。

    为什么要有__forwarding,这是个很重要的概念,下文我们在 blcok 的拷贝部分会同意处理

    Block 的 3 种类型

    我们可以写一段代码来测试一下 block 的类型

    int gi = 0;
    int main(int argc, const char * argv[]) {
        int i = 0;
        static int si= 0;
       void (^block)(void) = ^void()
        {
        };
        NSLog(@"没有引用任何变量= %@", block);
        
        block = ^void()
        {
            int inside = i;
        };
        NSLog(@"使用局部变量= %@", block);
    
        
       block = ^void()
        {
            int inside = si;
        };
        NSLog(@"使用局部静态变量 =%@", block);
    
        
       block = ^void()
        {
            int inside = gi;
        };
        NSLog(@"使用全局变量 = %@", block);
        
        
        NSLog(@"ARC 不参与赋值操作(未引用任何变量) %@", ^{});
        NSLog(@"ARC 不参与赋值操作(使用全局变量) %@", ^{int n = gi;});
        NSLog(@"ARC 不参与赋值操作(使用局部变量) %@", ^{int n = i;});
        NSLog(@"ARC 不参与赋值操作(使用局部静态变量) %@", ^{int n = si;});
    
        return 0;
    }
    

    输出的 log 为

    2016-09-25 00:50:40.664318 Block[5731:231257] 没有引用任何变量= <__NSGlobalBlock__: 0x100001060>
    2016-09-25 00:50:40.664561 Block[5731:231257] 使用局部变量= <__NSMallocBlock__: 0x100202780>
    2016-09-25 00:50:40.664586 Block[5731:231257] 使用局部静态变量 =<__NSGlobalBlock__: 0x1000010c0>
    2016-09-25 00:50:40.664625 Block[5731:231257] 使用全局变量 = <__NSGlobalBlock__: 0x100001100>
    2016-09-25 00:50:40.664639 Block[5731:231257] ARC 不参与赋值操作(未引用任何变量) <__NSGlobalBlock__: 0x100001140>
    2016-09-25 00:50:40.664678 Block[5731:231257] ARC 不参与赋值操作(使用全局变量) <__NSGlobalBlock__: 0x100001180>
    2016-09-25 00:50:40.664710 Block[5731:231257] ARC 不参与赋值操作(使用局部变量) <__NSStackBlock__: 0x7fff5fbff770>
    2016-09-25 00:50:40.664725 Block[5731:231257] ARC 不参与赋值操作(使用局部静态变量) <__NSGlobalBlock__: 0x1000011e0>
    Program ended with exit code: 0
    

    由此我们可以得出结论

    • _NSConcreteStackBlock:
      只用到外部局部变量、成员属性变量,且没有强指针引用的block都是StackBlock。
      StackBlock的生命周期由系统控制的,一旦返回之后,就被系统销毁了。

    • _NSConcreteMallocBlock:
      有强指针引用或copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,没有强指针引用即销毁,生命周期由程序员控制

    • _NSConcreteGlobalBlock:
      没有用到外界变量或只用到全局变量、静态变量的block为_NSConcreteGlobalBlock,生命周期从创建到应用程序结束。

    ARC 下的 Block 类型

    在 ARC 下,任何的 block 对强引用变量赋值操作将会触发 block 的 copy 操作,如上文中 第二个 block 的输出,照规则来说应该是 _NSConcreteStackBlock
    ,但是此 block 赋值给了 block 变量,所以这时候会变成了_NSConcreteMallocBlock,如果 block 变量由__weak修饰,那么不会发生 copy 也就是还是原来的_NSConcreteStackBlock

    Block 的 copy 相关

    在变量捕获那一部分,我们看到了 copy 和 dispose 相关的内容,这些内容是拷贝和释放相关的部分内容,我们一起来探讨一下。

    copy

    拷贝方法我们可以在系统库中看到_Block_copy方法,方法也是开源点击查看

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

    内部调用了 _Block_copy_internal方法

    简化版_Block_copy_internal

    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;
    }
    

    可以看出内部处理手段,通过 Block 的 descriptor 获取 Block 大小,之后直接 malloc 在堆上创建一个新个 block ,之后使用memmove拷贝内存空间,将 isa 设置为 _NSConcreteMallocBlock ,最后如果是 Block 是 copy 的话,调用 block->descriptor->copy简化版代码出处

    block->descriptor->copy 是什么玩意呢,
    在 block 的 descriptor 中,新增了 2 个函数指针 copy 和 dispose,指向相应 block 的捕获变量的 assign(基础类型) 和 retain (对象)这也是为什么 block 会循环引用所在
    copy 这个指针最后会调用_Block_object_assign,根据不同类型,会传入不同 flags调用不同方法

    /*
     * 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 里面引用了对象类型会传入BLOCK_FIELD_IS_OBJECT。最终会调用_Block_retain_object来 retain 对象,使用_Block_assign来赋值。
    • 如果 block 里面引用了 Block 会传入BLOCK_FIELD_IS_BLOCK最终也使用_Block_copy_internal来进行 copy
    • 如果 block 里面引用了__block变量,会传入BLOCK_FIELD_IS_BYREF,使用_Block_byref_assign_copy来设置 forwarding 指向,这个我们后面说。

    dispose

    释放过程的简化版的代码如下

    void _Block_release(void *arg) {
        // 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);
        }
    }
    

    代码比较简单,最终也可以看到会调用Block->descriptor->dispose,这和 copy 是相对应的,可以自己去挖掘一下,这边就不写出来了。

    __Block 中的 __forwarding

    前文__block部分我们会发现出现__forwarding这个东西,既然存在必然有存在的意义,__forwarding在初始化的时候我们可以看到是指向自己的。

    这里有几个情况,

    1. 当 block 是 _NSConcreteStackBlock 的时候,这个时候__forwarding指向自己,在执行的函数中,使用 __forwarding->xxx 访问变量。或者设置 __forwarding->xxx 来修改 __block 指向的对象,则也是为什么 __block 可以修改外界变量的原因。
    2. 当一个 block 被 copy 情况下呢,如果这个时候栈上的 block 已经出了作用域,如果 __forwarding 还是自己,那么必然指向已经释放的地址,这不是我们要的结果,上一节中,谈到过 Block 被 copy 会 malloc 出一片新空间,那么 __block 变量也会被 copy,这时候将 __forwarding 指向堆上的那个 block 的 __forwarding,这时候 __forwarding->xxx 就是访问堆上的新的变量,即使出了作用域,我们的结果也是正确的。

    下面有一张图可以加深理解

    1194012-5f5f486bab68191f

    图片来自http://www.jianshu.com/p/ee9756f3d5f6

    那么 __forwarding 是如何被修改的呢?
    还记得上一下小结提到的_Block_byref_assign_copy吗。我们可以看下实现。

    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_IS_GC) {
            ;   // don't need to do any more work
        }
        else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
            //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) {
                // 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);
    }
    

    可以看出 __block 在 copy 的时候会设置 __forwarding

    copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
    src->forwarding = copy; 
    

    小结

    Block 的在提供便利性的同时也会引入循环引用等问题,我们需要知道如何合理使用 Block。当然 Block 还有大量内容需要去研究。

    相关文章

      网友评论

          本文标题:Block

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