美文网首页
iOS Block学习笔记(六) -- Block与__bloc

iOS Block学习笔记(六) -- Block与__bloc

作者: brownfeng | 来源:发表于2018-11-23 16:07 被阅读11次

    我们看Block可以保存静态局部变量的地址, 通过这种方式修改静态局部变量的值, 如果是使用的自动变量, 为何不能使用获取自动变量的地址, 然后在Block执行时候, 去设置该地址的值呢?

    因为自动变量超过作用域以后内存自动就会废弃, 因此Block在其作用域以外去修改自动变量的值就会出问题, 这种使用场景很正常, 例如:

    typedef int (^blk_t)(int);
    blk_t func(int rate) {
      return ^(int count) { return rate * count; };
    }
    

    实际中, Block可以存储超过其变量作用域的被截获对象的自动变量.

    静态局部变量的地址在静态变量初始化作用域以外的地方也是可以访问的, 因为静态变量存储在数据存储区(.data区), 而非栈区.

    要使得Block能够修改自动变量, 需要使用__block存储域类型说明符.

    C语言中有以下存储域类型说明符:

    • typedef -- 该说明符声明在其作用于内引入一个名称.
    • extern -- 使用 extern 存储类说明符声明的变量是对名称与在程序的任何源文件的外部级别定义的名称相同的变量的引用。 内部 extern 声明用于使外部级别变量定义在块中可见。 除非在外部级别另有声明,否则使用 extern 关键字声明的变量仅在声明它的块中可见.
    • static -- 使用 static 存储类说明符在内部级别声明的变量具有全局生存期,但它仅在声明它的块中可见。 对于常量字符串,使用 static 会很有用,因为它减少了频繁初始化经常调用的函数的开销. 默认将变量存储到数据区.
    • auto -- auto 存储类说明符可声明自动变量,即具有本地生存期的变量。默认会将变量存储在栈区.
    • register -- 请求编译器尽可能的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率.

    __block说明符类似于static, auto 和 register, 用于指定将变量设置到哪个存储区.

    __block标志符保证栈上的自动变量被销毁以后, 仍然能够使用该变量, 或者修改该变量的值.

    其原理就是: 将栈上用__block修饰的自动变量, 封装成一个结构体, 然后让其创建在堆上, 当我们访问在栈上或者在堆上的访问和修改同一个变量.

    1. 如何理解栈上和堆上如何访问同一个变量: 一般情况调用__block int val = 10;语句, 该变量会创建在栈上, 在某些特定的时候时候, 该值会被从栈上copy到堆上, 因此会有两个变量, 底层通过指针让两个变量, 指向了同一个值.
    2. 注意 Block变量__block变量的区别.

    如果有以下源码:

    int main() {
      __block int val = 10;
      void (^blk)(void) = ^{val = 1;};
      return 0;
    }
    

    将源码编译以后:

    // 1. __block int val = 10; --> 添加__block以后的自动变量, 实际变成一个结构体. 注意内部的成员变量
    struct __Block_byref_val_0 {
      void *__isa; // 当前__block变量的类型
      __Block_byref_val_0 *__forwarding; // 指向本类的指针!!!!!!
      int __flags;
      int __size;
      int val; // 结构体包装的值!!!!!
    };
    
    // 2. Block的"基类"
    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    
    // 3. 实际 Block使用的结构体, 注意成员变量, 由于使用__block修饰, 成员变量是__Block_byref_count_0 *val, `__block变量`的结构体指针
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
    
      __Block_byref_count_0 *val; // __block修饰以后的int val, 用结构体包装, 然后这里存储的是该结构体的地址.
    
      // 注意构造函数中需要使用`__Block_byref_val_0 *_val`, 并且将`_val->__forwarding`赋值给`Block变量`
      __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;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    //Block的执行函数
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      // 1. 直接从Block中的的成员变量的取出 `__block变量`的地址
      __Block_byref_val_0 *val = __cself->val; // bound by ref
      
      // 2. 给val地址中的`__block变量`的`__forwarding指针`指向的`__block变量`的`val`成员赋值.
      (val->__forwarding->val) = 1;
    }
    
    // copy函数 - 后面当Block被从栈上copy到堆上时`__Block_byref_count_0 *val`使用的copy函数
    static void __main_block_copy_0(struct __main_block_impl_0 *dst,struct __main_block_impl_0 *src) {
      _Block_object_assign(&dst->val, src->val, BLOCK_FIELD_IS_BYREF);
    }
    
    // dispose函数 - 后面当Block被从堆上被销毁时,`__Block_byref_count_0 *val`使用的dispose函数
    static void __main_block_dispose_0(struct __main_block_impl_0 *src) {
      _Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
    }
    
    static struct __main_block_desc_0 {
      unsigned long reserved;
      unsigned long Block_size;
    
      // __Block_byref_count_0 *val 会使用的copy函数指针
      void (*copy)(struct __main_block_impl_0 *, struct __main_block_impl_0 *);
      // __Block_byref_count_0 *val 会使用的dispose函数指针
      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() {
      // __block变量, 被封装成结构体实例, 注意 , `__forwarding`指针,指向 val的地址!!!!, 最后一个参数val = 10, 表示实际val的当前值.
      __Block_byref_val_0 val = { 0, &val, 0, sizeof(__Block_byref_val_0), 10};
    
      // 创建Block变量时, 第三个参数是, 实际的 `__block`变量实例的地址.
      blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);
    }
    
    Block3.5.jpg

    当创建__block int val = 10, 这个变量时, 在栈中的内存结构如上图, 它的__forwarding指针指向自己:

    在执行Block语法以后, Block的内存在栈上,并且Block中的__Block_byref_val_0 *val指针也是指向__block int val = 10变量的地址的.

    通过两个内存图, 我们可以清晰的看出在Block的在栈上执行的流程, 如果此时执行blk->FuncPtr, 其中(val->__forwarding->val) = 1;就会直接修改栈上__block int val的值.

    还有一种情况, 当Block执行copy函数, 从栈上复制到堆上以后, 如上图右侧:

    Block会在堆上创建同栈上一样的内存结构体, 并且Block会主动调用__main_block_impl_0->Desc->copy函数将BLOCK_FIELD_IS_BYREF类型的成员变量__Block_byref_val_0 *val指向的内容(其实就是__block int val)拷贝到堆上, 并且将栈上的Block的__Block_byref_val_0->__forwarding指针指向堆上的__Block_byref_val_0 val.这时, 堆上的Block和__block变量不会受到栈上内存释放的影响, 从而达到存储超过其变量作用域的被截获对象的自动变量的效果. 这种情况后面会详细讲解.

    参考资料

    1. <<Objective-C 高级编程: iOS与OSX多线程和内存管理>>
    2. https://blog.csdn.net/deft_mkjing/article/details/53149629

    相关文章

      网友评论

          本文标题:iOS Block学习笔记(六) -- Block与__bloc

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