在做 iOS 开发的时候经常会需要使用到 block,简单来说 block 就是一个匿名函数,但是相比于常规意义上的函数,它除了能执行相应的代码逻辑、传递参数之外,还有一个比较重要的特性,就是 block 会捕获变量,根据变量类型(作用域、生命周期)的不同,block 会对其有不同的捕获方式,先列总结:
| 捕获方式 | 是否安全 | 是否可写 | |
|---|---|---|---|
| 全局变量 | 不捕获,可直接访问 | 是 | 是 |
| 局部变量 | 复制捕获 | 普通变量是,指针否 | 否 |
| 静态局部变量 | 指针捕获 | 是 | 是 |
| __block 修饰局部变量 | 指针捕获 | 否 | 是 |
详细描述
全局变量
首先,全局变量的作用域是整个程序,生命周期也与程序一致,所以无论是否在 block 内部,还是其他地方都可以直接访问这个变量,所以 block 并未对其做额外处理。
局部变量
局部变量的作用域与生命周期都十分有限,所以 block 对局部变量的处理是直接拷贝一份,不过对于不同类型的变量,其复制方式也不尽相同:
- 基础变量,基础变量直接复制一份内存,使得在 block 中对它的使用是完全完全的,但是不能进行赋值
- 普通指针,普通指针则是直接复制指针,所以这里对指针指向对象的访问是不安全的,但可以修改指针的指向
- OC 对象,OC 对象又分为强引用和弱引用,block 则会根据对象的引用类型选择是采用哪种引用方式捕获这个对象,这里也就说明了为什么 block 容易引起循环引用导致内存泄漏
静态局部变量
静态局部变量的生命周期与程序一致,但是作用域受到限制,所以 block 对静态局部变量的捕获方式一律是复制捕获其指针,这样既可以充分发挥静态变量生命周期长的优势,也可以获得静态变量的写能力;在 block 中对静态局部变量写操作时,OC 的编译器会将其改写成修改捕获的指针的指向。
__block 局部变量
__block 关键字修饰的局部变量,在 OC 编译器的操作下,既可以保持它局部变量的特点,同时增加了对它的写能力,大体上就是编译器会将 __block 修饰的变量编译成一个 struct,所有对 __block 变量的操作,都会改写成对 struct 的操作,而 block 对其的捕获行为,就是直接捕获这个 struct 的指针,从而支持在 block 中也可以实现对 __block 变量的赋值,本质上与静态局部变量类似,都是直接捕获指针,但是由于此刻的 struct 是个局部变量,那在 blcok 中对其的使用就是不安全的,使用时需额外注意。
代码验证
最后,编写一个测试类,来验证上述描述,首先 OC 类:
int gi = 3; // 全局变量
NSObject *go = nil; // 全局 OC 对象
static int sgi = 3; // 静态变量
@interface WRBlockTest ()
@property (nonatomic, assign) int mi; // 类变量
@property (nonatomic, strong) NSObject *mo; // 类变量
@end
@implementation WRBlockTest
- (void)wrTest {
static int si = 0; // 静态局部变量
int i = 1; // 局部变量
__block int bi = 2; // __block 变量
int *pi = &i; // 普通指针
NSObject *obj = [NSObject new]; // OC 对象
static NSObject *so = nil; // 静态局部变量
__block NSObject *bo = [NSObject new]; // __block 变量
NSObject __strong *sobj = obj; // 强引用对象
NSObject __weak *wobj = obj; // 弱引用对象
dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_NO_QOS_CLASS, ^{
// block 中操作捕获的变量
si = i;
bi = i;
gi = i;
_mi = i;
sgi = i;
*pi = i;
so = obj;
bo = obj;
go = obj;
_mo = obj;
_mo = sobj;
_mo = wobj;
NSLog(@"block");
});
block();
// 函数中操作变量
si = 0;
i = 1;
bi = 2;
gi = 3;
_mi = 4;
sgi = 3;
so = obj;
bo = obj;
go = obj;
_mo = obj;
}
@end
然后使用 clang 将其改写成 C++ 代码:
clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations WRBlockTest.m
改写之后相关部分的代码截取:
// 几个全局变量的声明,直接移过来
int gi = 3;
NSObject *go = __null;
static int sgi = 3;
// __block int bi 对应的结构体
struct __Block_byref_bi_0 {
void *__isa;
__Block_byref_bi_0 *__forwarding;
int __flags;
int __size;
int bi;
};
// __block NSObject *bo 对应结构体
struct __Block_byref_bo_1 {
void *__isa;
__Block_byref_bo_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *__strong bo;
};
// block 的结构体
struct __WRBlockTest__wrTest_block_impl_0 {
struct __block_impl impl;
struct __WRBlockTest__wrTest_block_desc_0* Desc;
// 静态局部变量,捕获其指针
int *si;
// 局部变量,直接复制
int i;
// 当 block 中操作类变量时,复制捕获类对象
WRBlockTest *const __strong self;
// 普通指针,直接复制
int *pi;
// 静态局部变量,捕获其指针
NSObject *__strong *so;
// 普通 OC 对象,强引用捕获
NSObject *__strong obj;
// 强引用 OC 对象,强引用捕获
NSObject *__strong sobj;
// 弱引用 OC 对象,弱引用捕获
NSObject *__weak wobj;
// __block 变量,捕获结构体指针
__Block_byref_bi_0 *bi; // by ref
// __block 变量,捕获结构体指针
__Block_byref_bo_1 *bo; // by ref
};
// block 代码块的实现
static void __WRBlockTest__wrTest_block_func_0(struct __WRBlockTest__wrTest_block_impl_0 *__cself) {
// 先从 block 结构体中取出所有捕获变量
__Block_byref_bi_0 *bi = __cself->bi; // bound by ref
__Block_byref_bo_1 *bo = __cself->bo; // bound by ref
int *si = __cself->si; // bound by copy
int i = __cself->i; // bound by copy
WRBlockTest *const __strong self = __cself->self; // bound by copy
int *pi = __cself->pi; // bound by copy
NSObject *__strong *so = __cself->so; // bound by copy
NSObject *__strong obj = __cself->obj; // bound by copy
NSObject *__strong sobj = __cself->sobj; // bound by copy
NSObject *__weak wobj = __cself->wobj; // bound by copy
// 静态变量复制,改写成更改指针指向
(*si) = i;
// __block 变量赋值,改写成操作结构体指针
(bi->__forwarding->bi) = i;
// 全局变量直接赋值
gi = i;
// 类成员变量赋值,捕获 self,通过 self 修改变量值
(*(int *)((char *)self + OBJC_IVAR_$_WRBlockTest$_mi)) = i;
// 静态变量直接复制
sgi = i;
// 指针操作
*pi = i;
(*so) = obj;
(bo->__forwarding->bo) = obj;
go = obj;
(*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_WRBlockTest$_mo)) = obj;
(*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_WRBlockTest$_mo)) = sobj;
(*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_WRBlockTest$_mo)) = wobj;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_mj_8nq80vvn19d_xkl6yftpz7j80000gn_T_WRBlockTest_07b21c_mi_0);
}
// OC 方法的函数改写
static void _I_WRBlockTest_wrTest(WRBlockTest * self, SEL _cmd) {
static int si = 0;
int i = 1;
// __block 修饰变量的声明,被改写成 struct 的声明
__attribute__((__blocks__(byref))) __Block_byref_bi_0 bi = {(void*)0,(__Block_byref_bi_0 *)&bi, 0, sizeof(__Block_byref_bi_0), 2};
int *pi = &i;
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
static NSObject *so = __null;
__attribute__((__blocks__(byref))) __Block_byref_bo_1 bo = {(void*)0,(__Block_byref_bo_1 *)&bo, 33554432, sizeof(__Block_byref_bo_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"))};
NSObject __attribute__((objc_ownership(strong))) *sobj = obj;
NSObject __attribute__((objc_ownership(weak))) *wobj = obj;
dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_NO_QOS_CLASS, ((void (*)())&__WRBlockTest__wrTest_block_impl_0((void *)__WRBlockTest__wrTest_block_func_0, &__WRBlockTest__wrTest_block_desc_0_DATA, &si, i, self, pi, &so, obj, sobj, wobj, (__Block_byref_bi_0 *)&bi, (__Block_byref_bo_1 *)&bo, 570425344)));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
si = 0;
i = 1;
// 对 __block 变量的赋值,都被改写成对 struct 的操作
(bi.__forwarding->bi) = 2;
gi = 3;
(*(int *)((char *)self + OBJC_IVAR_$_WRBlockTest$_mi)) = 4;
sgi = 3;
so = obj;
(bo.__forwarding->bo) = obj;
go = obj;
(*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_WRBlockTest$_mo)) = obj;
}