Blocks的实现

Block的实质

通过-rewrite-objc将Block语法转为C++源代码

//源代码
void(^blk)(void) = ^{
    printf("Block\n")
}
  1. block是"带有自动变量值的匿名函数",

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("Block\n")
    }

    可以看到其执行内容会按照普通的C函数处理,并且Block会根据所属的函数名(main函数)和该block出现的顺序值(为0)进行命名。并且其函数的执行参数即为__cself也就是指向该block的机构体的指针

  2. Block其实是Object-C对象

    block转换为结构体后其结构如下:

    struct __main_block_impl_0 {
        struct __block_impl impl;
    struct __main_block_desc_0 *Desc;
    //结构体初始化函数
    __main_block_impl_0(void *fp, strct __main_block_desc_0, int flags=0) {
    impl.isa = & NSConcreatStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
    }
    }
    struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FunctionPtr;
    }
    struct __main_block_desc_0 {
    unsigned long reserved; //今后版本升级所需的区域
    unsigned long Block_size; //block的大小
    }

    因此将block结构体展开的话,其初始化结构如下:

    static struct __main_block_desc_0 __main_block_desc_0_DATA = {
        0,
    sizeof(struct __main_block_impl_0) //block大小
    }
    //初始化
    isa = &_NSConcreatStackBlock;
    Flags=0;
    Reserved=0;
    FuncPtr=__main_block_func_0;
    Desc= &__main_block_desc_0_DATA;

    当执行该block时,即执行block中的FuncPtr函数,然后将block结构体指针作为参数传入:

    ((void(*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct _block_impl*)blk)
    

    注意

    对于isa=&_NSConcreatStackBlock;因为block也是一个对象,因此与其它OC对象类型,其isa指针指向其类结构体,即block的类信息就放在_NSConcreatStackBlock

截获自动变量

所谓的"截获自动变量值"其实是在执行block时,Block语法表达式所使用的自动变量被保存到Block的结构体实例中

截获了自动变量的结构体

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0 *Desc;
    const char *fmt; //自动变量值
    int val; //自动变量值
        //结构体初始化函数
     __main_block_impl_0(void *fp, strct __main_block_desc_0, const char*_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
        impl.isa = & NSConcreatStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
}

注意

block只会捕获使用到的变量,表达式中没有使用到的变量不会被自动追加

__block说明符

看到上面👆捕获变量的源码,可以看出来,block中捕获的自动变量仅捕获自动变量的值。因此在block内部重写该自动变量也不会改变原来捕获的自动变量值,因此,当我们给捕获的自动变量赋值时 就会产生编译错误

静态变量、静态全局变量、全局变量

我们可以在block中直接访问或修改静态全局变量/全局变量 这是没有任何改变的,因此可以在block转换后的函数中直接使用,而无需在结构体中保留、变量

但是对于静态变量,因为block转换后的函数已经在静态变量所处的函数之外,出了访问的作用域

//结构体中
__main_block_impl_0 {
    ...
    int * static_val; //block内部保留 静态变量的指针地址
}

//执行函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    (*static_val) *= 3 //
}

因此 block将静态变量的指针传递给__main_block_impl_0结构体的构造函数,并保留在结构体中,这是超出作用范围最简单的访问方法了

注意

对于自动变量为什么没有采用类似静态变量的访问方式呢??
Block可以截获超过作用域被截获的对象的自动变量,变量作用域结束的,原来的自动变量被废弃,因此block中超过变量作用域的对象就如同静态变量一样,无法通过指针进行访问了

__block修饰符

在OC中存在以下几种存储域类说明符:

  • typedef
  • extern
  • static
  • auto
  • register

__block就类似static、auto、register说明符,用于指定将变量设置到哪个存储区域中。(auto表示作为自动变量存储到栈中、static作为静态变量存储到数据区)

//源代码
__block int val = 10
void(^blk)(void) = ^{val = 1}

block结构体持有了指向__block变量结构体指针

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0 *Desc;
    __Block_byref_val_0 *val;
        //结构体初始化函数
     __main_block_impl_0(void *fp, strct __main_block_desc_0, __Block_byref_val_0 *_val, int flags=0) : val(_val->_forwarding) {
        impl.isa = & NSConcreatStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
}

__block变量
__block变量如同block一般变为__Block_byref_val_0结构体实例

struct __Block_byref_val_0 {
    void *__isa;
    __Block_byref_val_0 *__forwarding;
    int __flags;
    int __size;
    int val; //相当于原自动变量
}
//其初始化
__Block_byref_val_0 val = {
    0,
    &val,
    0,
    sizeof(__Block_byref_val_0),
    10
}

结构体持有了相当持有原自动变量的成员变量, __Block_byref_val_0结构体实例的成员变量__forwarding持有指向该实例自身的指针。通过 __forwarding访问成员变量val。(成员变量val相当于原自动变量)

函数

^{val=1}
    
//源代码转换为
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_val_0 *val = _cself->val;
    
    (val->__forwarding->val) = 1
}

注意:

  1. 为什么将__block变量写为单独的结构体而不写在block结构体中? 因是为了可以在多个block中使用相同的__block变量
  2. 为了什么使用了__block变量 就可以改写值? 因为当变量添加了__block前缀,此时声明的就直接是一个结构体 我们之后访问、修改不论是在block内外 都是用的这个结构体

本章忽略内容:

  • Block超出变量作用域存在原因
  • block变量结构体成员forwading存在理由