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指针就指向堆上的变量地址; 确保使用该变量时访问的都是同一个对象。