在做 iOS 开发的时候经常会需要使用到 block,简单来说 block 就是一个匿名函数,但是相比于常规意义上的函数,它除了能执行相应的代码逻辑、传递参数之外,还有一个比较重要的特性,就是 block 会捕获变量,根据变量类型(作用域、生命周期)的不同,block 会对其有不同的捕获方式,先列总结:

捕获方式是否安全是否可写
全局变量不捕获,可直接访问
局部变量复制捕获普通变量是,指针否
静态局部变量指针捕获
__block 修饰局部变量指针捕获

详细描述

全局变量

首先,全局变量的作用域是整个程序,生命周期也与程序一致,所以无论是否在 block 内部,还是其他地方都可以直接访问这个变量,所以 block 并未对其做额外处理。

局部变量

局部变量的作用域与生命周期都十分有限,所以 block 对局部变量的处理是直接拷贝一份,不过对于不同类型的变量,其复制方式也不尽相同:

  1. 基础变量,基础变量直接复制一份内存,使得在 block 中对它的使用是完全完全的,但是不能进行赋值
  2. 普通指针,普通指针则是直接复制指针,所以这里对指针指向对象的访问是不安全的,但可以修改指针的指向
  3. 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;
}