iOS

iOS block实现原理

Posted by Maqy on January 17, 2018

block类

block根据所在内存区域不同,有三种类型: _NSConcreteStackBlock:分配在栈上 _NSConcreteClobalBlock:分配在数据区 _NSConcreteMallocBlock:分配在堆上

在声明block时,如果该block没有捕获外部变量,则是_NSConcreteClobalBlock类型; 捕获了外部变量则是_NSConcreteStackBlock类型; 通过copy生成的block是_NSConcreteMallocBlock类型; 堆上的block调用copy只是增加引用计数,不会再复制。

block变量捕获

block是一个对象,转译成c语言如下:

struct __block_impl {
    void *isa; // 指向该block对象的Class实例
    int Flags;
    int Reserved;
    void *FuncPtr; // block方法的函数地址
};
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;   // 指定内存大小
    int *static_k; // 如果捕获了局部静态变量,则赋给该属性
    int val;       // 如果捕获了自动变量,赋值给该属性

    // 构造函数,传入初始化值
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_val) {
        impl.isa = &_NSConcreteStackBlock; 
        impl.Flags = flags;
        impl.FuncPtr = fp; // 构造传入block方法的函数地址
        Desc = desc;
    }
};

可以看出: 1.全局变量或全局静态变量,并不会被引用,调用时直接使用变量本身; 2.局部静态变量,会通过引用的形式被保存,即实际上block内部使用该变量时,还是使用了变量本身; 3.自动变量被捕获时,只是通过一个新的变量保存了值,所以其值保存的是block声明之前的值,如果声明之后再改写值,并不会影响block内部变量的值; 4.block内不能改变变量值是编译器检测做的事情。

__block原理

__block使block内部可以改变变量值。

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding; // 指向自身
 int __flags;
 int __size;
 int val; // 对应类型的变量
};

__block的变量实质上是个结构体,内部变量var保存了声明的值。

block捕获__block变量时,会将该结构体的引用传入,block修改变量时,实际上是修改的结构体内的var变量,而操作的结构体实例都是原本的实例。

__forwarding指针

该指针指向__block变量本身地址。 当栈上的block拷贝到堆上时,block引用的__block变量也会拷贝到堆; 这时栈上的变量的__forwarding指针就指向堆上的变量地址; 确保使用该变量时访问的都是同一个对象。