1.block的本质
block本质上是一个OC对象,内部封装了函数调用和函数调用环境,底层结构如下图,第一位是__block_impl,第二位是__main_block_desc_0,后边是捕获的变量。
其中__block_impl中存有一个FuncPtr的函数指针,block通过该指针调用了内部的函数。
注意:当调用函数的时候理论上应该是block-->impl.FuncPtr这样调用,但是由于impl是block的第一个成员,所以impl的地址就是block的地址,底层通过强制转换(struct __block_impl)block-->FuncPtr,直接用block调用函数。
__main_block_desc_0中的Block_size存有这个block的内存大小
2.block变量捕获
为了保证block内部能够正常访问外部的变量,比如auto变量的作用域和存储在栈区(变量释放时机)的因素让block有个变量捕获机制。
变量类型 | 捕获到block内部 | 访问方式 | |
局部变量 | auto | √ | 值传递 |
static | √ | 指针传递 | |
全局变量 | × | 直接访问 |
注意:auto局部变量与static局部变量访问方式不一样
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
int age;
int *height;
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
height是static局部变量,传入的是这个变量的地址,所以如果外部在调用block之前,捕获变量之后修改了static变量的值,block内部访问到的height是修改过的值。
3.block的类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型,NSBlock的上一层是NSObject
1.__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
2.__NSStackBlock__ ( _NSConcreteStackBlock )
3.__NSMallocBlock__ ( _NSConcreteMallocBlock )
block类型的改变
block类型 | 环境 |
__NSGlobalBlock__ | 没有访问auto变量 |
__NSStackBlock__ | 访问了auto变量 |
__NSMallocBlock__ | __NSStackBlock__调用了copy |
注意:
如果block内部访问了类的属性或者self,都是访问了auto变量,比如
- (void)test
{
void (^block)(void) = ^{
NSLog(@"-------%d", [self name]);
NSLog(@"-------%d", _name);
NSLog(@"-------%d", self.name);
};
block();
}
这三种情况都是访问了auto变量,因为test方法底层是test(self,cmd)这样子的,其中一个默认参数就是self。如果第二条直接打印成员变量的情况,因为成员变量是属于self的,所以类似于self-->_name,所以图一中的block底层捕获的变量是对象类型的auto变量 NSObject *self(看类型了,也有可能是Person *self),而不是单独的name变量。