美文网首页
iOS - block - 捕获__block基本类型

iOS - block - 捕获__block基本类型

作者: felix6 | 来源:发表于2019-04-12 17:53 被阅读0次

    [toc]

    参考

    block - 捕获__block基本类型

    http://www.cocoachina.com/ios/20150106/10850.html

    https://www.jianshu.com/p/404ff9d3cd42

    OC代码

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            __block NSInteger val = 0; 
            
            // 访问 val, 实际访问的是 val.__forwarding->val , 此时在栈上
            NSLog(@"1_val = %ld - %p", val, &val); 
            
            void (^block)(void) = ^{
                // 这里捕获 val, 捕获的是被 __block 包装的 val 对象;
                // 与否被重新赋值没有关系;
                val = 1; 
                NSLog(@"2_val = %ld - %p", val, &val);
            };
          
            NSLog(@"3_val = %ld - %p", val, &val);
    
            block();
            NSLog(@"4_val = %ld - %p", val, &val);
        }
        return 0;
    }
    
    MRC 输出: (变量值 - 变量地址) 
    // __block变量始终在栈上, 而且是同一份地址
    // 因为block捕获的是val的地址, block没有被拷贝到堆, 那val也还是最初的val ★
    1_val = 0 - 0x7ffeefbff408 
    3_val = 0 - 0x7ffeefbff408
    2_val = 1 - 0x7ffeefbff408
    4_val = 1 - 0x7ffeefbff408
    
    ARC 输出:
    1_val = 0 - 0x7ffeefbff408 // block定义前: 栈地址
    3_val = 0 - 0x103308438 // block定义后: 堆地址 ★
    2_val = 1 - 0x103308438
    4_val = 1 - 0x103308438
    // block 访问 __block 修饰的局部变量, block定义前后, 局部变量指针的地址不一致, 且地址值相差较大, 说明变量 val 已拷贝到堆中, 且 block 外局部变量 val 的地址也被改为这个堆地址。
    
    分析:
    MRC下:

    block 默认不被 copy, block 始终在栈上; <block - 存储域>

    所以无论包内包外, 该对象都不被copy, 始终在栈上; 局部变量 (其结构体成员) 的地址也始终在栈上, 且始终是同一份地址;

    ARC下:
    • block 因被强指针引用, 在定义之后, 不管有没有被调用, block 就已经被拷贝到堆上了; <block - 存储域>

    • __block 将局部变量 var 包装成了对象(结构体) __Block_byref_val_0, 并生成其 var, 我们访问的 var, 实际是通过结构体实例 var 的成员指针 __forwarding 间接访问成员变量 var。

    • 变量 val 所包装成的的结构体 __Block_byref_val_0 实例 (对象的指针) :

      • 在 block 定义前, 在栈区。

      • 在 block 定义后, 结构体 val 的地址 被 block 捕获为成员变量, 随 block 拷贝到堆 ;

        此时, 包外的结构体 val 的 __forwarding 指向堆中val的新地址。<详见本文C++分析>

      • 后续对 val 的读写也都是在这个堆地址上进行, 无论是否触发block;

        故而block包内可以修改局部变量 val 的值。

    可见:

    __block 实现了变量堆栈地址的变更 , 而有些博客非所谓的 "写操作生效"。

    block 访问 __block 修饰的局部变量, 会将该变量同 block 一起 copy 到堆区;


    C++代码

    MRC / ARC 编译后代码一致:

    int main(int argc, const char * argv[]) {
        { __AtAutoreleasePool __autoreleasepool; 
         
            // 定义 __block局部变量  __block NSInteger val = 0; 
            // __block局部变量 val 被封装成了一个 __Block_byref_val_0 结构体类型的实例 val
            __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 0};
    
            // 访问 val 实际都是 val.__forwarding->val ★★
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_p5_mp3284bs2xb073r91w__n99r0000gn_T_main_060736_mi_0, (val.__forwarding->val), &(val.__forwarding->val));
    
            // 捕获的是结构体 val 的地址, 作为第3个入参
            void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_p5_mp3284bs2xb073r91w__n99r0000gn_T_main_060736_mi_2, (val.__forwarding->val), &(val.__forwarding->val));
         
            // block调用
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_p5_mp3284bs2xb073r91w__n99r0000gn_T_main_060736_mi_3, (val.__forwarding->val), &(val.__forwarding->val));
         
        }
        return 0;
    }
    
    
    // 捕获到的局部变量, 被封装成结构体类型 (包装成对象);
    // 其名称 __Block_byref_val_0 是根据捕获到的局部变量名 val 命名的
    // 这个结构体中包含了该实例本身的引用 __forwarding ★★
    // 访问变量 val, 实质访问的是结构体 __Block_byref_val_0 的成员变量 val (val.__forwarding->val)
    struct __Block_byref_val_0 {
         void *__isa; // 有isa, 对象的特征; 编译器将 __block 变量包装成了对象 ★
         __Block_byref_val_0 *__forwarding; // 该实例本身的引用 ★
         int __flags;
         int __size;
         NSInteger val; // 结构体内部保存的原始变量 ★
    };
    
    // block 本身被转换成了 __main_block_impl_0 结构体实例;
    // 该实例持有 __Block_byref_val_0 结构体实例的指针。
    struct __main_block_impl_0 {
        struct __block_impl impl;
        struct __main_block_desc_0* Desc;
      
        // 包装了局部变量的对象(结构体指针) ★★
        __Block_byref_val_0 *val; // by ref 
      
        // 构造函数, 注意第3个入参
        __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
            impl.isa = &_NSConcreteStackBlock; // 栈中的 block, 出栈时会被销毁 (见下面<分析>) ★ 
            impl.Flags = flags;
            impl.FuncPtr = fp;
            Desc = desc;
        }
    };
    
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        // 通过入参 __cself 找到其成员变量, 结构体 __Block_byref_val_0 的实例 val ;
        __Block_byref_val_0 *val = __cself->val; // bound by ref;
      
        // 通过 __forwarding 找到<活跃>的结构体val, 拿到初始的局部变量val;  ★
        (val->__forwarding->val) = 1;
        
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_p5_mp3284bs2xb073r91w__n99r0000gn_T_main_060736_mi_1, (val->__forwarding->val), &(val->__forwarding->val));
    }
    
    
    // 注意, 使用了 __block 修饰基本数据类型的局部变量, desc结构体中多了 copy 和 dispoose 函数 ★
    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 };
    
    
    // (目的destination 源source) 
    // 当 Block 从栈复制到堆时, 会调用 _Block_object_assign 函数持有该变量(相当于retain)。
    static void __main_block_copy_0 (struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
        _Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
    }
    // 当堆上的 Block 被废弃时, 会调用 _Block_object_dispose 函数释放该变量(相当于release)。
    static void __main_block_dispose_0 (struct __main_block_impl_0*src)     {
        _Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
    }
    

    分析

    首先提几点疑问:

    • 当 block 作为回调执行时, 局部变量 val 已经出栈了, 此时代码为什么还能work呢?
    • 为什么是通过成员变量 __forwarding , 而不是直接去访问结构体中需要修改的变量呢?

    • 从上述C++代码中可以看到, __main_block_impl_0 结构体构造函数中, isa指向的是 _NSConcreteStackBlock, 即在栈上生成。

      分配在全局的 block, 在作用域外也可以通过指针安全的访问。但栈上的 block, 如果它所属的作用域结束, 该Block就被废弃。

      同样, __block 变量在定义时, 也分配在栈上, 当超出该变量的作用域时, 该 __block 变量也会被废弃。

      咋办呢?

    _NSConcreteMallocBlock 登场了!

    苹果采用了将 Block 和 __block 变量从栈上复制到堆上, 来解决这个问题。

    复制到堆上的Block, 它的结构体成员变量 isa 将变为: impl.isa = &_NSConcreteMallocBlock;

    当 Block 被复制到堆上时, 其捕获的 __block 变量也会被复制到堆上, 此时堆上的 Block 持有相应的堆上的 __block 变量。

    当栈上的 Block 及捕获的变量超出它原本作用域时, 堆上的 Block 还可以继续存在。

    当堆上的 __block 变量没有持有者时, 它才会被销毁。(这里的思考方式和 objc 引用计数内存管理完全相同。)

    此时, 只要原先栈上的 __block 变量的成员变量 __forwarding 指向堆上的结构体实例, 就能够安全地访问。

    一般可以使用 copy 方法手动将 Block 或者 __block变量从栈复制到堆上。

    • 比如我们把 Block 做为类的属性访问时, 一般把该属性设为 copy。

    • 有些情况下我们可以不用手动复制, 参考<存储域 - 自动 copy到堆>


    __forwarding 指针图解

    栈上的 __block 变量访问自身, 如图:

    <img src="https://cdn.jsdelivr.net/gh/coder-felix/image/20200609225314.png" style="zoom:25%;" />

    __block变量被复制到堆, 此时栈上和堆上分别有一个 __block 变量 (结构体)

    <img src="https://cdn.jsdelivr.net/gh/coder-felix/image/20200609225355.png" style="zoom:25%;" />

    首先明确一点: block 和 __block 变量, 实质就是相应结构体的实例。

    • __block 变量在栈上时, __forwarding 指针就指向自己。

    • block 拷贝到堆上后, __block 变量随 block 也拷贝到堆上一份, 此时有两个 __block 变量:

      • 堆上的这个 __block 变量的 __forwarding 指针指向自己;

      • 原先栈上的 __block 变量的 __forwarding 指针改为指向堆上的 __block 变量。

    这个时候我们可以通过 val.__forwarding->val 访问变量。


    val.__forwarding->val

    拿到一个结构体 val, 然后通过它的 __forwarding 找到<活跃>的结构体 val, 从而访问结构体内部保存的原始局部变量 val

    • 如果此时结构体 val 还未被拷贝到堆, 那<活跃>的结构体 val 就是最开始定义的, 在栈上;

    • 如果此时结构体 val 已经被拷贝到堆, 那<活跃>的结构体 val 就是这个被拷贝到堆上的结构体;

    这保证了无论结构体 val 有没有被拷贝到堆, 无论是从堆上还是栈上访问, 访问到的原始局部变量始终是同一个。★

    注: <活跃> 是个人为方便理解而定义的:

    • 如果未被拷贝到堆, 那<活跃>就是指栈上的;
    • 如果已被拷贝到堆, 那<活跃>就是指堆上的;

    相关文章

      网友评论

          本文标题:iOS - block - 捕获__block基本类型

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