iOS的block
一:block的本质
1. block本质上是一个OC对象,它内部有个isa指针
2. block是封装了函数调用以及函数调用环境的OC对象
3. block的底层结构:

二:block的变量捕获(capture)
为了保证block内部可以正常访问外部的的变量,block有个变量捕获机制。
代码测试捕获记机制,使用 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 生成C++代码。
1. 局部变量的 auto变量:
int a =10;
void(^ksblock)(void) = ^ {
NSLog(@"%d",a);
};
a =20;
ksblock();
生成的C++代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
inta;
__main_block_impl_0(void*fp,struct__main_block_desc_0 *desc,int_a,intflags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看出ksblock将a变量捕获到了内部,并且是值传递的方式,所以打印出来结果就是10,并没有修改成功。
2. 局部变量的 static变量:
static int a =10;
void(^ksblock)(void) = ^ {
NSLog(@"%d",a);
};
a =20;
ksblock();
生成的C++代码如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int*a;
__main_block_impl_0(void*fp,struct__main_block_desc_0 *desc,int*_a,intflags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看出ksblock将a变量捕获到了内部,因为是指针传递,所有当把a修改成20的时候,block调用时候的a就是指针指向的最新值。
3. 全局变量:
inta=10;
intmain(intargc,constchar* argv[]) {
@autoreleasepool {
void(^ksblock)(void) = ^ {
NSLog(@"%d",a);
};
a=20;
ksblock();
}
return0;
}
生成的C++代码如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void*fp,struct__main_block_desc_0 *desc,intflags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看出ksblock并没有将全局变量a对象捕获到内部,只需要进行全局访问就可以了。
三:block的类型
block有三种类型,可以通过调用class方法或者isa指针查看类型,最终都是继承自NSBlock类型。
block类型环境:
__NSGlobalBlock__ 没有访问auto变量
__NSStackBlock__ 访问了auto变量
__NSMallocBlock__ __NSStackBlock__调用了copy
举例说明,在MRC环境下
void(^ksblock1)(void) = ^ {
};
NSLog(@"ksblock1 %@",[ksblock1 class]);
inta =10;
void(^ksblock2)(void) = ^ {
NSLog(@"%d",a);
};
NSLog(@"ksblock2 %@",[ksblock2 class]);
void(^ksblock3)(void) = [^ {
NSLog(@"%d",a);
}copy];
NSLog(@"ksblock3 %@",[ksblock3 class]);
打印结果:

但是在ARC环境下,ksblock2打印的却是__NSMallocBlock__,这是因为在arc中默认会将block从栈复制到堆上,而在非arc中,则需要手动copy。
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
1. block作为函数返回值时
2. 将block赋值给__strong指针时
3. block作为Cocoa API中方法名含有usingBlock的方法参数时
4. block作为GCD API的方法参数时
MRC下block属性的建议写法:
@property (nonatomic, copy) block;
ARC下block属性的建议写法:
@property (nonatomic, strong) block;
@property (nonatomic, copy) block;
四:__block修饰符
1. __block可以解决block内部无法修改auto变量值的问题
2. __block不能修饰全局变量、静态变量(static)
3. 编译器会将__block变量包装成一个对象
__block int a =10;
void(^ksblock)(void) = ^ {
NSLog(@"%d",a);
};
ksblock();
转成C++代码如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void*fp,struct__main_block_desc_0 *desc, __Block_byref_a_0 *_a,intflags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __Block_byref_a_0 {
void*__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
inta;
};
__Block_byref_a_0添加一个指向自身的指针,根据指针查找到对应的值。
五:block内存管理
1. 当block在栈上的时候,并不会对__block变量产生强引用。
2. 当block被copy到堆上的时候
- 会调用block内部的copy函数
- copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会对__block变量形成强引用(retain)

3. 当block从堆上移除的时候
- 会调用block内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放引用的__block变量(release)

六:循环引用问题处理
ARC:
用__weak、__unsafe_unretained解决
用__block解决(必须要调用block)
MRC:
用__unsafe_unretained解决
用__block解决