专注、坚持

Obj-C 中的 Block

2019.11.24 by kingcos
Date Notes Demo Source Code
2019-07-27 首次提交 - -
2019-11-24 重新整理部分内容;补充《Effective Objective-C 2.0》&《Objective-C 高级编程》相关内容 Block_in_Obj-C objc4-756.2libclosure-73

0

Preface

从 OS X Snow Leopard 和 iOS 4 开始,Apple 引入了 Block 的概念。Obj-C 中的 Block 即通常意义上的匿名函数,而之所以引入是因为 C 语言中并不支持匿名函数。这使得 Block 在 Obj-C 中可以称为是一类公民(First-Class Citizen),既可以作为类的属性,也可以作为函数参数或返回值来传递。那么本文就将着眼于此,谈谈 Obj-C 中的 Block。

Prerequisites

匿名函数与 Block

匿名函数顾名思义,即不带名称的函数,而 C 语言中即使是函数指针也仍然需要通过函数名来获得其地址:

// C 语言函数
int cFunc(int arg) {
    return arg;
}

int main_1(int argc, const char * argv[]) {
    int result1 = cFunc(10);

    // C 语言中的函数指针需要通过函数名来获得其地址
    int (*cFuncPtr)(int) = &cFunc;
    int result2 = (*cFuncPtr)(10);

    printf("result1 = %d, result2 = %d\n", result1, result2);

    return 0;
}

// OUTPUT:
// result1 = 10, result2 = 10

Obj-C 中的匿名函数被称为 Block,中文有译作块、闭包(Closure),其实很多语言都有类似的机制,比如 Swift 中的 Closure,Java / Python / C++ 11 中的 Lambda(λ)表达式,JavaScript 中的匿名函数等。

ARC & MRC

Obj-C 作为使用引用计数来进行内存管理语言,虽然 ARC(Automatic Reference Counting,自动引用计数)已经引入多年,但有时仍然需要通过 MRC(Mannul Reference Counting,手动引用计数)了解更加底层的细节与区别。在 Obj-C 中我们可以使用 __has_feature(objc_arc) 宏来区分 ARC 和 MRC 下可以编译的代码。

目前 Xcode 中默认为 ARC,如需切换为 MRC 可以通过 Xcode - Build Settings - Automatic Reference Counting - No 来设置:

3

⚠️ 注意:本文中若未指明 MRC 或未使用 __has_feature(objc_arc) 明确编译条件,则默认为 ARC。

What

语法

Obj-C 中的 Block 令人十分诟病的一个问题是其语法的繁杂。笔者也时常在 fuckingblocksyntax.com 查询相应的语法。

声明一个参数为 int 没有返回值的 Block:

// main_2

// 省略返回值的 void
^(int arg) {
    printf("%d\n", arg);
};

// 完整写法
^void (int arg) {
    printf("%d\n", arg);
};

在语法上,与 C 语言函数不同的是:Block 没有函数名以及 ^ 记号,后者作为特殊记号将便于查找。BNF(Backus Normal Form,又 Backus-Naur Form,译作巴科斯范式,又巴科斯-诺尔范式)是一种用来描述计算机编程语言语法符号集的规范,通过这个规范在识别每一条编程语言语句时即可构建抽象语法树,进而帮助编译,未来笔者将在编译原理相关内容中深入探究。Block 声明语法的 BNF 如下:

Block_literal_expression ::= ^ block_decl compound_statement_body
block_decl ::=
block_decl ::= parameter_list
block_decl ::= type_expression

因此通过上述 BNF 就能清晰的知道 Block 的几种声明语法:

// main_2

// 省略返回值 & 参数
^{ /* compound_statement_body */ };
// 省略返回值
^(int arg){ /* compound_statement_body */ };
// 省略参数
^int{ /* compound_statement_body */ return 0; };

Obj-C 中的 Block 与其它类型类似,可以作为属性或者变量,也可以在函数中作为参数或者返回值。但由于 Block 的语法繁杂,我们可以用 typedef 定义 Block 的类型,使得 API 更加简洁明了:

// BlockType_1 是一个参数和返回值均为 int 的 Block 类型
typedef int (^BlockType_1)(int);

@interface Foo_1 : NSObject
@property (nonatomic, strong) BlockType_1 block;
@end

@implementation Foo_1
// 作为对象方法参数
- (void)blockAsArg_1:(int(^)(int))block {
    block(10);
}
// 使用 typedef 作为对象方法参数
- (void)blockAsArg_2:(BlockType_1)block {
    block(10);
}
// 作为对象方法返回值
- (int(^)(int))blockAsReturnValue_1 {
    return ^(int arg) { return arg; };
}
// 使用 typedef 作为对象方法返回值
- (BlockType_1)blockAsReturnValue_2 {
    return ^(int arg) { return arg; };
}
@end

// Block 作为 C 语言函数参数
void blockAsArg_1(int (^block)(int)) {
    block(10);
}
// 使用 typedef 作为 C 语言函数参数
void blockAsArg_2(BlockType_1 block) {
    block(10);
}

// 《Objective-C 高级编程》中提到的,将 Block 以非 typedef 形式作为 C 语言函数的返回值类型;但目前会报错,且尚未找到相应的解决方法:
// ERROR: Block pointer to non-function type is invalid
// ERROR: Returning 'int (^)(int)' from a function with incompatible result type 'int (int)'
// int (^blockAsReturnValue()(int)) {
//     return ^(int arg) { return arg; };
// }

BlockType_1 blockAsReturnValue() {
    return ^(int arg) { return arg; };
}

int main_2(int argc, const char * argv[]) {
    // 声明 Block 类型的变量
    int (^block1)(int);
    // 声明并赋值 Block(省略返回值类型)
    int(^block2)(int) = ^(int arg) { return arg; };
    block2 = ^int(int arg) { return arg; };
    // Block 类型的变量之间赋值
    int (^block3)(int) = block2;

    block1 = block2;

    // 使用 typedef
    BlockType_1 block4;
    BlockType_1 block5 = ^(int arg) { return arg; };
    block5 = ^int(int arg) { return arg; };
    BlockType_1 block6 = block5;
    block4 = block5;

    return 0;
}

结构

main 函数中声明并执行一个简单的 Block:

int main_3(int argc, const char * argv[]) {
    void(^block)(void) = ^{
        NSLog(@"Hello, World!");
    };

    block();

    return 0;
}

// OUTPUT:
// Hello, World!

为了窥探 Block 的结构,可以尝试使用 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp 命令将以上代码翻译为 C/C++ 代码来分析:

// Block 实现的结构体
struct __block_impl {
  void *isa;     // isa 指针,即 Block 也是 id 类型,即 Obj-C 对象
  int Flags;     // 标记,默认会被初始化为 0
  int Reserved;  // 保留域(ABI 兼容),默认 0
  void *FuncPtr; // Block 代码块的函数指针
};

// ➡️ Block 结构体
struct __main_3_block_impl_0 {
  struct __block_impl impl;           // 实现(非指针)
  struct __main_3_block_desc_0* Desc; // 描述信息(指针)
  // 构造函数
  __main_3_block_impl_0(void *fp, struct __main_3_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock; // Block 作为 Obj-C 对象,那么 isa 将指向其类对象,即 _NSConcreteStackBlock
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// Block 内代码块封装在该 C 语言静态函数中,函数命名规则:__CALLER_METHOD_NAME_block_func_NUMBER
// 类似 Obj-C 实例方法参数 self 或 C++ 实例方法参数 this,__cself 为指向 Block 结构体的变量
static void __main_3_block_func_0(struct __main_3_block_impl_0 *__cself) {
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_b9596e_mi_0);
}

// Block 描述信息的结构体静态变量
static struct __main_3_block_desc_0 {
  size_t reserved;   // 保留域,默认 0
  size_t Block_size; // Block 大小,sizeof 整个 Block 结构体 ⬇️
} __main_3_block_desc_0_DATA = { 0, sizeof(struct __main_3_block_impl_0)};

// 主函数
int main_3(int argc, const char * argv[]) {
    // 通过 __main_3_block_impl_0 结构体的构造函数初始化,参数为静态函数和描述信息静态变量的地址,将地址存储在 block 变量中
    // 忽略类型转换:block = &__main_3_block_impl_0(__main_3_block_func_0, &__main_3_block_desc_0_DATA));
    void(*block)(void) = ((void (*)())&__main_3_block_impl_0((void *)__main_3_block_func_0, &__main_3_block_desc_0_DATA));

    // 执行 Block(参数 block 即静态函数中的参数 __cself)
    // 忽略类型转换:block->FuncPtr(block);
    // 在 __main_block_impl_0 结构体中,impl 是第一个变量,因此其与结构体本身的首地址一致,因此可以强转
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

当声明一个 Block 变量,其中的代码块会被封装到 __main_3_block_func_0 静态函数中(后缀 0 代表序号,后同),描述信息则被初始化并封装到 __main_3_block_desc_0_DATA 静态变量中;这两者在 main 函数中一起构造了 Block 结构体 __main_3_block_impl_0,并将其地址保存在声明的 block 变量中;由于 Block 的结构体直接包含了 __block_impl 结构体,因此在构造函数中,__block_implisa 指向了 _NSConcreteStackBlock 类对象的地址,也印证了 Block 的本质是特殊的 Obj-C 对象;__block_implFuncPtr 函数指针则指向包含代码块的静态函数,在 Block 真正执行时,将通过 block->FuncPtr(block) 找到静态函数并调用。上述代码中的 block 结构正如下图:

1

变量捕获

自动变量

自动变量(Automatic Variable)即局部作用域变量,指在代码块中声明的变量,也可以显式使用 auto 关键字声明。自动变量存储在内存的栈区,离开作用域时会被自动回收:

int main_4(int argc, const char * argv[]) {
    void (^block)(void);

    {
        auto int autoVar = 1;
        const char *autoConstVar = "kingcos.me";

        block = ^() {
            NSLog(@"autoVar == %d, autoConstVar == %s", autoVar, autoConstVar);
        };

        autoVar = 10;
        // const char * 为常量,不可改变存储的类型
        // autoConstVar = 1.5;
    }

    // ERROR: Use of undeclared identifier 'autoVar'
    // autoVar = 10;

    block();

    return 0;
}

// OUTPUT:
// autoVar == 1, autoConstVar == kingcos.me

如上,我们在代码块外声明了一个 Block,而在代码块内声明了自动变量,并将 block 赋值。超出代码块作用于后,我们已经无法再次访问代码块内部的变量,而 block 执行的结果却是正常的。这是为什么呢?

struct __main_4_block_impl_0 {
  struct __block_impl impl;
  struct __main_4_block_desc_0* Desc;
  int autoVar;              // 捕获的自动变量 autoVar
  const char *autoConstVar; // 捕获的变量类型与外界声明完全相同

  __main_4_block_impl_0(void *fp, struct __main_4_block_desc_0 *desc, int _autoVar, const char *_autoConstVar, int flags=0) : autoVar(_autoVar), autoConstVar(_autoConstVar) { // 构造函数初始化内结构体内部的自动变量
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_4_block_func_0(struct __main_4_block_impl_0 *__cself) {
  // 从 __celf 取初始化时获得的值
  int autoVar = __cself->autoVar; // bound by copy
  const char *autoConstVar = __cself->autoConstVar; // bound by copy

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_27d308_mi_3, autoVar, autoConstVar);
}

static struct __main_4_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_4_block_desc_0_DATA = { 0, sizeof(struct __main_4_block_impl_0)};

int main_4(int argc, const char * argv[]) {
    // 在代码块外声明的 Block 变量
    void (*block)(void);

    {
        auto int autoVar = 1;
        const char *autoConstV// 构造时将外部自动变量的值直接传入
        // 忽略类型转换:block = &__main_4_block_impl_0(__main_4_block_func_0, &__main_4_block_desc_0_DATA, autoVar, autoConstVar));
        block = ((void (*)())&__main_4_block_impl_0((void *)__main_4_block_func_0, &__main_4_block_desc_0_DATA, autoVar, autoConstVar));

        autoVar = 10;
    }

    // 执行 Block
    // 忽略类型转换:block->FuncPtr(block);
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

为了避免自动变量的提前释放,Block 会将其内部使用到的自动变量捕获到结构体内部(按值传递),这样即使超出自动变量的作用域或者在 Block 外更改了原有的变量值,也不会影响 Block 中已经被捕获的那个变量。

那么在 Obj-C 对象方法中如果有 Block 使用到了 self 或者成员变量呢?

@interface Foo_2 : NSObject
@property (nonatomic, copy) NSString *prop;

- (void)bar_1;
- (void)bar_2;

+ (void)bar;
@end

@implementation Foo_2
- (void)dealloc
{
    NSLog(@"dealloc");
}

- (void)bar_1 {
    void (^block_1)(void) = ^() {
        NSLog(@"self == %@", self);
    };

    block_1();
}

- (void)bar_2 {
    self.prop = @"kingcos.me";

    void (^block_2)(void) = ^() {
        // WARNING: Block implicitly retains 'self'; explicitly mention 'self' to indicate this is intended behavior
        // NSLog(@"_prop == %@.", _prop);

        NSLog(@"_prop == %@", self->_prop);
    };

    block_2();
}

+ (void)bar {
    void (^block)(void) = ^() {
        NSLog(@"self == %@", self);
    };

    block();
}
@end

int main_4(int argc, const char * argv[]) {
    Foo_2 *foo = [[Foo_2 alloc] init];
    [foo bar_1];
    [foo bar_2];

    [Foo_2 bar];

    return 0;
}

// OUTPUT:
// self == <Foo_2: 0x100532d80>
// _prop == kingcos.me
// self == Foo_2
// dealloc

Obj-C 方法里使用到的 self 来自于其默认参数(另一个默认参数是 _cmd),而函数参数的作用域也仅在当前函数体内。因此 Block 内如果使用到 self 或者成员变量(本质也是通过 self 进行访问的)则也会对其进行捕获:

struct __Foo_2__bar_1_block_impl_0 {
  struct __block_impl impl;
  struct __Foo_2__bar_1_block_desc_0* Desc;
  Foo_2 *const __strong self; // 捕获的 self

  __Foo_2__bar_1_block_impl_0(void *fp, struct __Foo_2__bar_1_block_desc_0 *desc, Foo_2 *const __strong _self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __Foo_2__bar_1_block_func_0(struct __Foo_2__bar_1_block_impl_0 *__cself) {
  // 执行时取出 self
  Foo_2 *const __strong self = __cself->self; // bound by copy

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_169b69_mi_1, self);
}

struct __Foo_2__bar_2_block_impl_0 {
  struct __block_impl impl;
  struct __Foo_2__bar_2_block_desc_0* Desc;
  Foo_2 *const __strong self; // 捕获的 self

  __Foo_2__bar_2_block_impl_0(void *fp, struct __Foo_2__bar_2_block_desc_0 *desc, Foo_2 *const __strong _self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __Foo_2__bar_2_block_func_0(struct __Foo_2__bar_2_block_impl_0 *__cself) {
  Foo_2 *const __strong self = __cself->self; // bound by copy

  // self + OBJC_IVAR_$_Foo_2$_prop:成员变量是根据 self 做的偏移
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_169b69_mi_3, (*(NSString *__strong *)((char *)self + OBJC_IVAR_$_Foo_2$_prop)));
}

struct __Foo_2__bar_block_impl_0 {
  struct __block_impl impl;
  struct __Foo_2__bar_block_desc_0* Desc;
  const Class self; // 对于类方法,则捕获的是类对象
  __Foo_2__bar_block_impl_0(void *fp, struct __Foo_2__bar_block_desc_0 *desc, const Class _self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __Foo_2__bar_block_func_0(struct __Foo_2__bar_block_impl_0 *__cself) {
  const Class self = __cself->self; // bound by copy

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_169b69_mi_4, self);
}

局部静态变量

局部静态变量指定义在代码块内的静态变量,其作用域是在代码块内,但其生命周期并不会随代码块结束:

int main_5(int argc, const char * argv[]) {
    void (^block)(void);

    {
        static int a = 1;

        block = ^() {
            NSLog(@"a == %d", a);
        };

        a = 10;
    }

    block();

    return 0;
}

// OUTPUT:
// a == 10

不同于自动变量,对于局部静态变量,由于其生命周期随程序退出才结束,因此在 Block 内部只需要将局部静态变量的地址进行捕获即可(按引用传递)。这样即使超过局部静态变量的作用域但 Block 中仍然可以通过地址访问到,且当 Block 执行前如果改变了局部静态变量的值,那么执行时也将根据地址获取到最新值:

struct __main_5_block_impl_0 {
  struct __block_impl impl;
  struct __main_5_block_desc_0* Desc;
  int *a; // 捕获的变量,类型为指向 int 类型的指针
  __main_5_block_impl_0(void *fp, struct __main_5_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_5_block_func_0(struct __main_5_block_impl_0 *__cself) {
  // int *a = &a;(外界 a 的地址)
  int *a = __cself->a; // bound by copy

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_0fafd0_mi_4, (*a));
}

static struct __main_5_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_5_block_desc_0_DATA = { 0, sizeof(struct __main_5_block_impl_0)};

int main_5(int argc, const char * argv[]) {
    void (*block)(void);

    {
        static int a = 1;

        // 忽略类型转换:block = &__main_5_block_impl_0(__main_5_block_func_0, &__main_5_block_desc_0_DATA, &a);(&a 即 a 的地址作为参数传入)
        block = ((void (*)())&__main_5_block_impl_0((void *)__main_5_block_func_0, &__main_5_block_desc_0_DATA, &a));

        // a 的值发生改变
        a = 10;
    }

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

全局变量

全局变量即定义在所有函数体外的变量,只有当程序退出时其生命周期和作用域才结束:

// 全局变量
int globalVar_1 = 1;
// 全局静态变量
static int staticGlobalVar_1 = 2;

int main_6(int argc, const char * argv[]) {
    void (^block)(void) = ^() {
        NSLog(@"globalVar_1 == %d, staticGlobalVar_1 == %d", globalVar_1, staticGlobalVar_1);
    };

    globalVar_1 = 10;
    staticGlobalVar_1 = 20;

    block();

    return 0;
}

// OUTOUT:
// globalVar_1 == 10, staticGlobalVar_1 == 20

因此对于全局变量,Block 并不会去捕获,而是在使用时直接进行读取即可:

int globalVar_1 = 1;
static int staticGlobalVar_1 = 2;

struct __main_6_block_impl_0 {
  struct __block_impl impl;
  struct __main_6_block_desc_0* Desc;
  // 没有捕获

  __main_6_block_impl_0(void *fp, struct __main_6_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_6_block_func_0(struct __main_6_block_impl_0 *__cself) {
  // 直接访问
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_d50b04_mi_8, globalVar_1, staticGlobalVar_1);
}

C 语言数组

需要注意的是,当 Block 并不能捕获 C 语言中的数组。实际上这在 C 语言的函数中也是不支持的,猜测这是因为 Block 遵循了类似 C 语言的规范(参考《Objective-C 高级编程》),这里可以使用指针代替:

void cFunc_2(char a[10]) {
    printf("%d\n", a[0]);
}

void cFunc_3(char a[10]) {
    // C 语言不允许数组类型变量赋值给另外的数组类型变量
    // ERROR: Array initializer must be an initializer list or string literal
    // char b[10] = a;
    // printf("%d\n", b[0]);
}

int main_7(int argc, const char * argv[]) {
    const char cLocalArr[] = "kingcos.me";
    const char *cLocalString = "kingcos.me";

    ^{
        // Block 不会对 C 语言数组进行捕获
        // ERROR: Cannot refer to declaration with an array type inside block
        // printf("%c\n", cLocalArr[7]);

        // 使用指针代替
        printf("%c\n", cLocalString[7]);
    }();

    // 猜测 Block 遵循了类似 C 语言的规范
    char a[10] = {2};

    cFunc_2(a);
    cFunc_3(a);

    return 0;
}

// OUTPUT:
// .
// 2

类型

Obj-C 中的 Block 根据其存储在不同的内存区域被分为三种:__NSGlobalBlock____NSStackBlock____NSMallocBlock__,它们又各自继承自 __NSGlobalBlock__NSMallocBlock__NSStackBlock,这些父类又都继承自 NSBlockNSBlock 又继承自 NSObject

int main_8(int argc, const char * argv[]) {
    void (^block1)(void) = ^() {
        NSLog(@"Hello, world!");
    };

    int a = 1;
    void (^block2)(void) = ^() {
        NSLog(@"%d", a);
    };

    NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
        NSLog(@"%d", a);
    } class]);

    NSLog(@"%@ %@ %@", [block1 superclass], [block2 superclass],[^{
        NSLog(@"%d", a);
    } superclass]);

    NSLog(@"%@ %@ %@", [[block1 superclass] superclass], [[block2 superclass] superclass],[[^{
        NSLog(@"%d", a);
    } superclass] superclass]);

    NSLog(@"%@ %@ %@", [[[block1 superclass] superclass] superclass], [[[block2 superclass] superclass] superclass],[[[^{
        NSLog(@"%d", a);
    } superclass] superclass] superclass]);

    return 0;
}

// OUTPUT:
// __NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__
// __NSGlobalBlock __NSMallocBlock __NSStackBlock
// NSBlock NSBlock NSBlock
// NSObject NSObject NSObject

⚠️ 注意:

在上一节中,我们可以从「翻译」后的 C++ 代码中看到 Block 内部的 isa 指针均默认指向了 _NSConcreteStackBlock,但其实在 macOS 的 Xcode 中运行时并不存在「翻译」这一步骤,因此我们最好使用运行时的方法来真正确定 Block 的具体类型。

__NSGlobalBlock__

内部没有访问外界自动变量的 Block 将在执行时不依赖上下文,该类型的 Block 属于 __NSGlobalBlock__,存储在内存的数据段(Data Section),该区域通常也会存放全局变量:

int globalVar_2 = 1;

int main_9(int argc, const char * argv[]) {
    void (^gloablBlock)(void) = ^{
        // 没有访问任何外界变量
        NSLog(@"This is a __NSGlobalBlock__.");
    };

    NSLog(@"%@", [gloablBlock class]);

    static int staticVar = 1;

    gloablBlock = ^{
        // 访问了全局变量或局部静态变量
        NSLog(@"globalVar2 == %d, staticVar == %d.", globalVar_2, staticVar);
    };

    NSLog(@"%@", [gloablBlock class]);

    // 该 Block 实际初始化时将:
    // impl.isa = &_NSConcreteGlobalBlock;

    return 0;
}

// OUTPUT:
// __NSGlobalBlock__
// __NSGlobalBlock__

__NSGlobalBlock__ 类型的 Block 执行 copy 操作并不会将其拷贝至堆中,而是仍将返回 __NSGlobalBlock__

__NSStackBlock__

What

自动变量存储在内存的栈区,当 Block 内访问了外界的自动变量时会对其进行捕获,此时 Block 本身也被分配在栈区,属于 __NSStackBlock__ 类型:

int main_10(int argc, const char * argv[]) {
#if !__has_feature(objc_arc)
    int c = 1;

    void (^stackBlock)(void) = ^{
        NSLog(@"c == %d.", c);
    };

    NSLog(@"%@", [stackBlock class]);
#endif

    // stackBlock 初始化时将:
    // impl.isa = &_NSConcreteStackBlock;

    return 0;
}

// OUTPUT:
//  __NSStackBlock__

栈区的内存不需要开发者手动管理,当作用域结束时栈区内存将会被自动回收。所以即使 __NSStackBlock__ 类型的 Block 内部捕获了自动变量,但 Block 本身和捕获的变量也仍然存储在栈区,会随着其作用域结束而释放:

@interface Foo_3 : NSObject
@end

@implementation Foo_3
- (void)dealloc
{
#if !__has_feature(objc_arc)
    [super dealloc]; // MRC 下需手动调用下父类的 dealloc
#endif
    NSLog(@"dealloc");
}
@end

void (^stackBlock_1)(void);
void (^stackBlock_2)(void);

void initBlockInARC() {
#if __has_feature(objc_arc)
    // 初始化,引用计数 +1
    Foo_3 *strongF = [[Foo_3 alloc] init];

    NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(strongF)));

    // 弱引用,引用计数不变 +0
    __weak Foo_3 *weakF = strongF;

    NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(strongF)));

    // Block 捕获,引用计数 +1
    // __NSStackBlock__
    ^{
        NSLog(@"%@", strongF);
        NSLog(@"%@", weakF);
    };

    NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(strongF)));
#endif
}

void initBlockInMRC() {
#if !__has_feature(objc_arc)
    int c = 10;

    stackBlock_1 = ^{
        NSLog(@"c == %d", c);
    };

    // 初始化,引用计数 +1
    Foo_3 *f = [[Foo_3 alloc] init];

    NSLog(@"%ld", [f retainCount]);

    // MRC 下 stackBlock_2 捕获 f 但不持有,引用计数 +0
    stackBlock_2 = ^{
       NSLog(@"f == %@", f);
    };

    NSLog(@"%ld", [f retainCount]);

    [f release];
#endif
}

int main_11(int argc, const char * argv[]) {
#if __has_feature(objc_arc)
    // 初始化 stackBlock
    initBlockInARC();
#else
    // 初始化 stackBlock
    initBlockInMRC();

    // 执行 stackBlock_1
    stackBlock_1(); // c == -272632744
    // 执行 stackBlock_2
    stackBlock_2(); // CRASH: objc[59614]: Attempt to use unknown class 0x7ffeefbff428.

    NSLog(@"stackBlock_1 is a %@.", [stackBlock_1 class]); // CRASH: EXC_BAD_ACCESS
#endif

    return 0;
}

// MRC OUTPUT:
// 1
// 1
// dealloc

// ARC OUTPUT:
// 1
// 1
// 2
// dealloc

MRC 下,__NSStackBlock__ 类型的 Block 中虽然捕获了对象类型的自动变量,但没有改变其引用计数,即不会进行 retain 持有操作。而 main 函数中执行到 Block 时其本身已经被系统回收,捕获的自动变量 cf 也已经从栈上释放,因此访问时出现了脏数据,访问 Block 本身也出现了 EXC_BAD_ACCESS 崩溃。为了避免这种问题,我们需要将 Block 分配在堆上(即 __NSMallocBlock__),这是因为堆区的内存由开发者自己管理,可以避免被自动回收。

ARC 下,__NSStackBlock__ 类型的 Block 中捕获了 strongPweakP(可以通过为翻译 Obj-C 代码的命令额外添加 -fobjc-arc -fobjc-runtime=ios-13.0.0 参数强制使用 ARC 并指定运行时平台和版本,这样即可输出带有部分运行时关键字的 C/C++ 代码,如下),虽然强引用会导致其引用计数增加,但由于 Block 本身在栈上并仍会随作用域超出而销毁,捕获的自动变量同理也会被销毁。

struct __initBlockInARC_block_impl_0 {
  struct __block_impl impl;
  struct __initBlockInARC_block_desc_0* Desc;
  Foo_3 *__strong strongF; // ⚠️ 强引用
  Foo_3 *__weak weakF;     // ⚠️ 弱引用

  __initBlockInARC_block_impl_0(void *fp, struct __initBlockInARC_block_desc_0 *desc, Foo_3 *__strong _strongF, Foo_3 *__weak _weakF, int flags=0) : strongF(_strongF), weakF(_weakF) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

__NSMallocBlock__

What

__NSMallocBlock__ 类型的 Block 存储在内存的堆区,堆区是通常用作动态分配(Malloc)的内存:

@interface Foo_4 : NSObject
@end

@implementation Foo_4
- (void)dealloc
{
#if !__has_feature(objc_arc)
    [super dealloc];
#endif
    NSLog(@"dealloc");
}
@end

typedef void(^BlockType_2)(void);

void (^mallocBlock_1)(void);
void (^mallocBlock_2)(void);

void initBlock() {
#if !__has_feature(objc_arc)
    int c = 10;

    mallocBlock_1 = [^{
        NSLog(@"c == %d", c);
    } copy];

    Foo_4 *f = [[Foo_4 alloc] init];

    mallocBlock_2 = [^{
        NSLog(@"%@", f);
    } copy];

    [f release];
#else
    int c = 10;

    mallocBlock_1 = ^{
        NSLog(@"c == %d", c);
    };

    Foo_4 *strongF = [[Foo_4 alloc] init];
    __weak Foo_4 *weakF = strongF;

    // 1⃣️ ARC 下强指针 mallocBlock_2 指向的 Block 会被自动拷贝为 __NSMallocBlock__
    mallocBlock_2 = ^{
        NSLog(@"%@", strongF);
        NSLog(@"%@", weakF);
    };

    // 使用弱引用指针指向的 Block,将仍然是 __NSStackBlock__,编译将提示以下警告
    // WARNING: Assigning block literal to a weak variable; object will be released after assignment
    __weak BlockType_2 weakStackBlock = ^{
        NSLog(@"%@", strongF);
        NSLog(@"%@", weakF);
    };

    NSLog(@"weakStackBlock is %@", weakStackBlock);

    // 3⃣️ Block 作为 Cocoa API 中方法名含有 `usingBlock` 的参数时会被自动拷贝为 __NSMallocBlock__
    // 4⃣️ Block 作为 GCD API 参数时会被自动拷贝为 __NSMallocBlock__
#endif
}

BlockType_2 returnMallocBlock() {
    auto int autoVar = 10;

#if !__has_feature(objc_arc)
    // MRC 下如果直接返回 Block,将报错「Returning block that lives on the local stack」
    // 即编译器已经发现返回的 Block 是在栈上,一旦函数体走完,Block 就会被销毁,因此在 MRC 下需要手动 copy:
    return [^{
        NSLog(@"%d", autoVar);
    } copy];
#else
    // 2⃣️ ARC 下 Block 作为函数返回值会被自动拷贝为 __NSMallocBlock__
    return ^{
        NSLog(@"%d", autoVar);
    };
#endif
}

int main_12(int argc, const char * argv[]) {
    initBlock();

    NSLog(@"mallocBlock_1 is %@", [mallocBlock_1 class]);
    NSLog(@"mallocBlock_2 is %@", [mallocBlock_2 class]);
    NSLog(@"returnMallocBlock() is %@", [returnMallocBlock() class]);

    mallocBlock_1();
    mallocBlock_2();

#if !__has_feature(objc_arc)
    [mallocBlock_1 release];
    [mallocBlock_2 release];
#endif

    return 0;
}

// MRC OUTPUT:
// mallocBlock_1 is __NSMallocBlock__
// mallocBlock_2 is __NSMallocBlock__
// returnMallocBlock() is __NSMallocBlock__
// c == 10
// <Foo_4: 0x100559170>
// dealloc

// ARC OUTPUT:
// weakStackBlock is <__NSStackBlock__: 0x7ffeefbff3d0>
// mallocBlock_1 is __NSMallocBlock__
// mallocBlock_2 is __NSMallocBlock__
// returnMallocBlock() is __NSMallocBlock__
// c == 10
// <Foo_4: 0x1022a7e40>
// <Foo_4: 0x1022a7e40>
// ⚠️:此处 mallocBlock_1 & mallocBlock_2 全局变量还没有释放,因而持有的 strongF 也没有被释放
  • MRC 下,对于 __NSStackBlock__ 类型的 Block,只要再对其执行 copy 操作(在 C 语言中对应为 Block_Copy)即可从栈区拷贝到堆区。Block 和其中捕获的自动变量将均在堆区,在主动释放前,就都可以访问到;
  • ARC 下,需要我们手动 copy 或者编译器会根据情况将 __NSStackBlock__ 类型的 Block 自动 copy 到堆上:
    1. 强指针指向的 Block 会被自动拷贝;
    2. Block 作为函数返回值会被自动拷贝;
    3. Block 作为 Cocoa API 中方法名含有 usingBlock 的参数时会被自动拷贝;
    4. Block 作为 GCD API 参数时会被自动拷贝。
  • 当拷贝到堆上后,Block 将把 _NSConcreteMallocBlock 类对象写入到 isa 中,即 isa = &_NSConcreteMallocBlock

因此,Block 作为属性时应当使用 copystrong 修饰(推荐 copy):

@interface Foo_5 : NSObject
#if !__has_feature(objc_arc)
// MRC
@property (nonatomic, copy) void (^block_1)(void);
#else
// ARC
@property (nonatomic, copy) void (^block_2)(void);
@property (nonatomic, strong) void (^block_3)(void);
#endif
@end

@implementation Foo_5
@end

__NSStackBlock__ 拷贝到堆上的细节

上一节提到,ARC 下,编译器会根据情况将 __NSStackBlock__ 类型的 Block 自动 copy 到堆上,那么在「拷贝」时到底会发生什么呢?

这里以返回一个 __NSStackBlock__ 的函数为例:

#if __has_feature(objc_arc)
typedef int(^BlockType_3)(int);

BlockType_3 returnSomeBlock(int arg) {
    return ^(int param){ return arg * param; };
}
#endif

int main_13(int argc, const char * argv[]) {
#if __has_feature(objc_arc)
    NSLog(@"%@", returnSomeBlock(10));
#endif

    return 0;
}

// OUTPUT:
// <__NSMallocBlock__: 0x1006baa60>

虽然 returnSomeBlock 函数返回了一个栈上的 Block,但其实 ARC 已经为我们默默将其拷贝到堆。尝试将上述代码翻译为 C/C++ 代码:

BlockType_3 returnSomeBlock(int arg) {
    return ((int (*)(int))&__returnSomeBlock_block_impl_0((void *)__returnSomeBlock_block_func_0, &__returnSomeBlock_block_desc_0_DATA, arg));
}

但从这里似乎并不能看到 ARC 的所作所为。通过一番查阅,ARC 是在代码生成阶段插入了 retain / release LLVM 位代码(Bitcode),所以只能选择在汇编层面一探究竟了:

Block_in_Obj-C`returnSomeBlock:
    0x100003390 <+0>:  pushq  %rbp
    0x100003391 <+1>:  movq   %rsp, %rbp
    0x100003394 <+4>:  subq   $0x30, %rsp
    0x100003398 <+8>:  movl   %edi, -0x4(%rbp)
->  0x10000339b <+11>: movq   0xc5e(%rip), %rax         ; (void *)0x00007fff8dfbbe40: _NSConcreteStackBlock
    0x1000033a2 <+18>: movq   %rax, -0x28(%rbp)
    0x1000033a6 <+22>: movl   $0xc0000000, -0x20(%rbp)  ; imm = 0xC0000000
    0x1000033ad <+29>: movl   $0x0, -0x1c(%rbp)
    0x1000033b4 <+36>: leaq   0x35(%rip), %rax          ; __returnSomeBlock_block_invoke at main.m:551
    0x1000033bb <+43>: movq   %rax, -0x18(%rbp)
    0x1000033bf <+47>: leaq   0x1022(%rip), %rax        ; __block_descriptor_36_e8_i12?0i8l
    0x1000033c6 <+54>: movq   %rax, -0x10(%rbp)
    0x1000033ca <+58>: movl   -0x4(%rbp), %edi
    0x1000033cd <+61>: movl   %edi, -0x8(%rbp)
    0x1000033d0 <+64>: leaq   -0x28(%rbp), %rdi
    0x1000033d4 <+68>: callq  0x100003ac0               ; symbol stub for: objc_retainBlock
    0x1000033d9 <+73>: movq   %rax, %rdi
    0x1000033dc <+76>: addq   $0x30, %rsp
    0x1000033e0 <+80>: popq   %rbp
    0x1000033e1 <+81>: jmp    0x100003a8a               ; symbol stub for: objc_autoreleaseReturnValue

如何查看 Xcode 项目的汇编代码呢?

  1. 在 Block 内部打个断点,当运行至断点时,Xcode Menu - Debug - Debug Workflow - Always Show Disassembly,即可自动跳转至汇编视图;
  2. 使用 Mac 自带的 otool -tvV PRODUCT_PATH 命令即可将 Xcode 编译后的二进制产物(Products 目录下)的汇编指令打印出来。

这里我们暂时没必要搞清楚每一条汇编指令,而是着重看一下右侧的注释 objc_retainBlockobjc_autoreleaseReturnValue 与 ARC 有关的指令,因此就基本可以得出 returnSomeBlock 在 ARC 下的伪代码(同时参考了《Objective-C 高级编程》):

BlockType_3 returnSomeBlock(int arg) {
    // 构造 Block,赋值给 tmp 变量(默认是强指针引用)
    BlockType_3 __strong tmp = &__returnSomeBlock_block_impl_0(
        __returnSomeBlock_block_func_0,
        &__returnSomeBlock_block_desc_0_DATA,
        arg
    );
    // 根据 objc4 - NSObject.mm,等同 tmp = _Block_copy(tmp);,_Block_copy 的源码 Apple 开源在 libclosure
    // 将栈上的 Block 拷贝到堆上,并将堆上 Block 地址返回给 tmp 保存
    tmp = objc_retainBlock(tmp);
    // 注册到自动释放池并返回
    return objc_autoreleaseReturnValue(tmp);
}

除了上述四个条件,ARC 对于 Block 并不是完全「可靠」的:

typedef void(^BlockType_4)(void);

NSArray *returnBlocksArray_1() {
    int autoVar = 1;

    return [[NSArray alloc] initWithObjects:
            ^{ NSLog(@"%d", autoVar); },
            ^{ NSLog(@"%d", autoVar); },
            nil];
}

int main_14(int argc, const char * argv[]) {
    NSArray *arr_1 = returnBlocksArray_1();
    BlockType_4 block_1 = (BlockType_4)[arr_1 objectAtIndex:0];
    block_1();
    // CRASH: EXC_BAD_ACCESS
    // block_1 = (BlockType_4)[arr_1 objectAtIndex:1];

    return 0;
}

// OUTPUT:
// 1

在使用 initWithObjects: API 构建 NSArray 数组时,编译器并没有把作为参数且放置在栈上的 Block 自动拷贝到堆上,当栈区内存空间回收后去执行 Block 从而导致了崩溃:

NSArray *returnBlocksArray_2() {
    int autoVar = 10;

    // 拷贝到堆上
    return [[NSArray alloc] initWithObjects:
            [^{ NSLog(@"%d", autoVar); } copy],
            [^{ NSLog(@"%d", autoVar); } copy],
            nil];
}

NSArray *returnBlocksArray_3() {
    int autoVar = 100;

    // 使用强指针指向
    BlockType_4 block = ^{ NSLog(@"%d", autoVar); };

    return [[NSArray alloc] initWithObjects:
            block,
            block,
            nil];
}

int main_14(int argc, const char * argv[]) {
    NSArray *arr_2 = returnBlocksArray_2();
    BlockType_4 block_2 = (BlockType_4)[arr_2 objectAtIndex:0];
    block_2();
    block_2 = (BlockType_4)[arr_2 objectAtIndex:1];
    block_2();

    NSArray *arr_3 = returnBlocksArray_3();
    BlockType_4 block_3 = (BlockType_4)[arr_3 objectAtIndex:0];
    block_3();
    block_3 = (BlockType_4)[arr_3 objectAtIndex:1];
    block_3();

    return 0;
}

// OUTPUT:
// 10
// 10
// 100
// 100

即使在 ARC 下,我们有时也需要手动调用 copy 将 Block 拷贝至堆上或使用强指针指向,而对同一个 Block 多次 copy 会有什么问题么?

int main_14(int argc, const char * argv[]) {
    int autoVar = 0;
    BlockType_4 block_4 = ^{ NSLog(@"%d", autoVar); };
    block_4 = [[[block_4 copy] copy] copy];

    NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(block_4)));

    return 0;
}

// OUTPUT:
// 1

其实只有第一次 copy 时,栈上的 Block 将被拷贝至堆上一份,后续的 copy 则是对堆上的拷贝进行拷贝,即仅仅递增引用计数。如 block_4 = [[[block_4 copy] copy] copy]; 则最终仍只有 block_4 指向堆上的 Block,因此其引用计数仍为 1

不同于基础类型,对于对象类型的自动变量总是更多地涉及到内存管理方面:

@interface Foo_6 : NSObject
@end

@implementation Foo_6
- (void)dealloc
{
    NSLog(@"dealloc");
}
@end

int main_15(int argc, const char * argv[]) {
    Foo_6 *strongF = [[Foo_6 alloc] init];

    __weak Foo_6 *weakF = strongF;

    void(^someBlcok)(void) = ^{
        NSLog(@"%@", strongF);
        NSLog(@"%@", weakF);
    };

    someBlcok();

    return 0;
}

// OUTPUT:
// <Foo_6: 0x1007ab750>
// <Foo_6: 0x1007ab750>
// dealloc

将上述翻译为 C/C++ 代码:

struct __main_15_block_impl_0 {
  struct __block_impl impl;
  struct __main_15_block_desc_0* Desc;
  Foo_6 *__strong strongF; // 默认强引用
  Foo_6 *__weak weakF;     // 显式弱引用
  __main_15_block_impl_0(void *fp, struct __main_15_block_desc_0 *desc, Foo_6 *__strong _strongF, Foo_6 *__weak _weakF, int flags=0) : strongF(_strongF), weakF(_weakF) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_15_block_func_0(struct __main_15_block_impl_0 *__cself) {
  Foo_6 *__strong strongF = __cself->strongF; // bound by copy
  Foo_6 *__weak weakF = __cself->weakF; // bound by copy

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_e96639_mi_49, strongF);
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_e96639_mi_50, weakF);
}

// 栈上的 Block 拷贝到堆时,会调用 __main_block_copy_0 方法
static void __main_15_block_copy_0(struct __main_15_block_impl_0*dst, struct __main_15_block_impl_0*src) {
    // _Block_object_assign 函数会根据自动变量的修饰符(__strong / __weak)作出相应的操作,形成强引用或弱引用(类似 Retain,将对象赋值在对象类型的结构体成员变量中)
    // BLOCK_FIELD_IS_OBJECT 为 flags,函数内部根据此参数决定相应的处理逻辑;该 flag 对应为 _Block_retain_object
    _Block_object_assign((void*)&dst->strongF, (void*)src->strongF, 3/*BLOCK_FIELD_IS_OBJECT*/); // 对外界 strongF 强引用
    _Block_object_assign((void*)&dst->weakF, (void*)src->weakF, 3/*BLOCK_FIELD_IS_OBJECT*/);     // 对外界 weakF 弱引用
    // _Block_object_assign 的源码 Apple 开源在 libclosure,此处暂略
}

// 堆上的 Block 销毁时,会调用 __main_15_block_dispose_0 方法,类似对象的 dealloc 方法
static void __main_15_block_dispose_0(struct __main_15_block_impl_0*src) {
    // _Block_object_dispose 函数使得自动变量的引用计数减一(类似 Release,释放赋值在对象类型的结构体成员变量中的对象)
    // BLOCK_FIELD_IS_OBJECT 对应为 _Block_release_object(object);
    _Block_object_dispose((void*)src->strongF, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_dispose((void*)src->weakF, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static struct __main_15_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  // 当在 Block 中访问对象类型的自动变量时会增加额外的域来协助管理内存
  void (*copy)(struct __main_15_block_impl_0*, struct __main_15_block_impl_0*);
  void (*dispose)(struct __main_15_block_impl_0*);
} __main_15_block_desc_0_DATA = { 0, sizeof(struct __main_15_block_impl_0), __main_15_block_copy_0, __main_15_block_dispose_0};

int main_15(int argc, const char * argv[]) {
    Foo_6 *strongF = ((Foo_6 *(*)(id, SEL))(void *)objc_msgSend)((id)((Foo_6 *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Foo_6"), sel_registerName("alloc")), sel_registerName("init"));

    __attribute__((objc_ownership(weak))) Foo_6 *weakF = strongF;

    void(*someBlcok)(void) = ((void (*)())&__main_15_block_impl_0((void *)__main_15_block_func_0, &__main_15_block_desc_0_DATA, strongF, weakF, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)someBlcok)->FuncPtr)((__block_impl *)someBlcok);

    return 0;
}

__block

本质

上面的例子中 Block 内部只是访问了外界的变量,接下来我们尝试下在 Block 内修改变量值:

// 静态全局变量
static int staticGlobalVar = 0;
// 全局变量
int globalVar = 0;

int main_16(int argc, const char * argv[]) {
    // 自动变量
    int autoVar = 0;
    // 静态变量
    static int staticVar = 0;

    auto NSMutableArray *autoArray = [[NSMutableArray alloc] init];

    ^{
        // ERROR: Variable is not assignable (missing __block type specifier)
        // autoVar = 10;

        staticVar = 10;
        staticGlobalVar = 10;
        globalVar = 10;

        [autoArray addObject:@"kingcos.me"];

        // ERROR: Variable is not assignable (missing __block type specifier)
        // autoArray = [[NSMutableArray alloc] init];
    }();

    return 0;
}

对于局部静态变量和全局静态变量,Block 内可以直接修改这些变量的值。而对于自动变量编译器则会报错 Variable is not assignable (missing __block type specifier)(变量未分配(丢失 __block 类型限定符)),那么这里的 __block 是什么呢?

int main_17(int argc, const char * argv[]) {
    void(^block1)(void);
    void(^block2)(void);

    {
        __block int autoVar = 1;
        __block auto NSMutableArray *autoArray = [[NSMutableArray alloc] init];

        NSLog(@"autoVar = %d", autoVar);

        block1 = ^{
            autoVar = 10;
            autoArray = [[NSMutableArray alloc] init];

            NSLog(@"autoVar = %d", autoVar);
        };

        block2 = ^{
            autoVar = 20;

            NSLog(@"autoVar = %d", autoVar);
        };

        NSLog(@"autoVar = %d", autoVar);
    }

    block1();
    block2();

    return 0;
}

// OUTPUT:
// autoVar = 1
// autoVar = 1
// autoVar = 10
// autoVar = 20

如上,使用 __block 修饰的自动变量,在 Block 内部确实可以修改,尝试将以上代码翻译为 C++:

// 将自动变量包装的结构体
struct __Block_byref_autoVar_0 {
  void *__isa; // 标志着这是个 Obj-C 对象
__Block_byref_autoVar_0 *__forwarding; // 结构体本身的地址
 int __flags; // 标志位
 int __size;  // sizeof 结构体的大小
 int autoVar; // 自动变量,初始化时赋值
};

struct __Block_byref_autoArray_1 {
  void *__isa;
__Block_byref_autoArray_1 *__forwarding;
 int __flags;
 int __size;
 // 内存管理方法
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
  NSMutableArray *__strong autoArray; // 对象类型的自动变量
};

// Block 结构体
struct __main_17_block_impl_0 {
  struct __block_impl impl;
  struct __main_5_block_desc_0* Desc;
  // 不使用 __block 时为 int autoVar;,此时为指向 __Block_byref_autoVar_0 的指针
  __Block_byref_autoVar_0 *autoVar; // by ref
  // 不使用 __block 时为 NSMutableArray *autoArray;,此时为指向 __Block_byref_autoArray_1 的指针
  __Block_byref_autoArray_1 *autoArray; // by ref
  __main_17_block_impl_0(void *fp, struct __main_5_block_desc_0 *desc, __Block_byref_autoVar_0 *_autoVar, __Block_byref_autoArray_1 *_autoArray, int flags=0) : autoVar(_autoVar->__forwarding), autoArray(_autoArray->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_17_block_func_0(struct __main_17_block_impl_0 *__cself) {
  // 指向结构体的指针
  __Block_byref_autoVar_0 *autoVar = __cself->autoVar; // bound by ref
  __Block_byref_autoArray_1 *autoArray = __cself->autoArray; // bound by ref

  // 通过指向自身的指针改变了值
  (autoVar->__forwarding->autoVar) = 10;
  (autoArray->__forwarding->autoArray) = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
}

static void __main_17_block_copy_0(struct __main_17_block_impl_0*dst, struct __main_17_block_impl_0*src) {
    _Block_object_assign((void*)&dst->autoVar, (void*)src->autoVar, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_assign((void*)&dst->autoArray, (void*)src->autoArray, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __main_17_block_dispose_0(struct __main_17_block_impl_0*src) {
    _Block_object_dispose((void*)src->autoVar, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_dispose((void*)src->autoArray, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static struct __main_17_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_17_block_impl_0*, struct __main_17_block_impl_0*);
  void (*dispose)(struct __main_17_block_impl_0*);
} __main_17_block_desc_0_DATA = { 0, sizeof(struct __main_17_block_impl_0), __main_17_block_copy_0, __main_17_block_dispose_0};

// 第二个 Block
struct __main_17_block_impl_1 {
  struct __block_impl impl;
  struct __main_17_block_desc_1* Desc;
  __Block_byref_autoVar_0 *autoVar; // by ref
  __main_17_block_impl_1(void *fp, struct __main_17_block_desc_1 *desc, __Block_byref_autoVar_0 *_autoVar, int flags=0) : autoVar(_autoVar->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_17_block_func_1(struct __main_17_block_impl_1 *__cself) {
  __Block_byref_autoVar_0 *autoVar = __cself->autoVar; // bound by ref

  (autoVar->__forwarding->autoVar) = 20;
}

static void __main_17_block_copy_1(struct __main_17_block_impl_1*dst, struct __main_17_block_impl_1*src) {
    _Block_object_assign((void*)&dst->autoVar, (void*)src->autoVar, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __main_17_block_dispose_1(struct __main_17_block_impl_1*src) {
    _Block_object_dispose((void*)src->autoVar, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static struct __main_17_block_desc_1 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_17_block_impl_1*, struct __main_17_block_impl_1*);
  void (*dispose)(struct __main_17_block_impl_1*);
} __main_17_block_desc_1_DATA = { 0, sizeof(struct __main_17_block_impl_1), __main_17_block_copy_1, __main_17_block_dispose_1};

int main_17(int argc, const char * argv[]) {
    void(*block1)(void);
    void(*block2)(void);

    {
        // 初始化 __Block_byref_autoVar_0 结构体,将 autoVar 的地址赋值给结构体中的 __forwarding 指针
        // __Block_byref_autoVar_0 autoVar = {0, &autoVar, 0, sizeof(__Block_byref_autoVar_0), 0(初始值)}
        __attribute__((__blocks__(byref))) __Block_byref_autoVar_0 autoVar = {(void*)0,(__Block_byref_autoVar_0 *)&autoVar, 0, sizeof(__Block_byref_autoVar_0), 0};

        // 初始化 __Block_byref_autoArray_1 结构体,将 autoArray 的地址赋值给结构体中的 __forwarding 指针
        // __Block_byref_autoArray_1 autoArray = {0, &autoArray, 33554432, sizeof(__Block_byref_autoArray_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, NSMutableArray(初始值)}
        __attribute__((__blocks__(byref))) auto __Block_byref_autoArray_1 autoArray = {(void*)0,(__Block_byref_autoArray_1 *)&autoArray, 33554432, sizeof(__Block_byref_autoArray_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"))};

        // 外界通过 __forwarding 访问 __block 变量
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_8f352d_mi_52, (autoVar.__forwarding->autoVar));

        // 忽略类型转换:block1 = &__main_17_block_impl_0(__main_17_block_func_0, &__main_17_block_desc_0_DATA, &autoVar, &autoArray, 570425344);
        block1 = ((void (*)())&__main_17_block_impl_0((void *)__main_17_block_func_0, &__main_17_block_desc_0_DATA, (__Block_byref_autoVar_0 *)&autoVar, (__Block_byref_autoArray_1 *)&autoArray, 570425344));

        // 忽略类型转换:block2 = &__main_17_block_impl_1(__main_17_block_func_1, &___main_17_block_desc_1_DATA, &autoVar, 570425344);
        block2 = ((void (*)())&__main_17_block_impl_1((void *)__main_17_block_func_1, &__main_17_block_desc_1_DATA, (__Block_byref_autoVar_0 *)&autoVar, 570425344));

        // 外界通过 __forwarding 访问 __block 变量
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_8f352d_mi_55, (autoVar.__forwarding->autoVar));
    }

    ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
    ((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);

    return 0;
}

__blockautoVar 封装到 __Block_byref_autoVar_0 结构体中,将 autoArray 封装到 __Block_byref_autoArray_1 结构体中,其中的 isa 指针标志着该结构体本质也属于 Obj-C 对象;__forwarding 指针指向了该结构体本身,在初始化时被赋值为声明的结构体的地址;__size 为该结构体的大小信息;autoVarautoArray 为捕获的变量本身。因此捕获了 __block 变量的 Block 结构如下图所示:

4

__forwarding

当 Block 被分配在栈上时,其内部使用到的 __block 变量也会被分配在栈上;当 Block 超出作用域被销毁时,__block 变量则也会被销毁。当 Block 从栈拷贝到堆上时,其内部使用到的 __block 变量也会同时被拷贝到堆上,并被堆上的 Block 持有。当多个 Block 使用同一个 __block 变量时,第一个被拷贝到堆上的 Block 会将其同时拷贝到堆上并被该 Block 持有;当其它 Block 被拷贝到堆上后,将持有该变量,引用计数递增。

int main_18(int argc, const char * argv[]) {
    void(^block1)(void);
    void(^block2)(void);

    {
        // 声明 __block 变量 blockVar
        __block int blockVar = 10;

        // blockVar 默认在栈上
        NSLog(@"1 - %p", &blockVar); // 1 - 0x7ffeefbff3d8

        // 栈上的 Block 捕获,Block 本身在栈上
        NSLog(@"2 - %p", ^{
            NSLog(@"%p", &blockVar);
        }); // 2 - 0x7ffeefbff388

        // 栈上的 Block 捕获对 blockVar 无影响
        NSLog(@"3 - %p", &blockVar); // 3 - 0x7ffeefbff3d8

        // 栈上的 Block 捕获并执行
        ^{
            // 捕获的 blockVar 仍在栈上
            NSLog(@"4 - %p", &blockVar); // 4 - 0x7ffeefbff3d8
        }();

        // 栈上的 Block 执行对 blockVar 无影响
        NSLog(@"5 - %p", &blockVar);

        // 栈上的 Block 被强指针引用,将拷贝至堆上
        block1 = ^{
            // 捕获的 blockVar 也在堆上
            NSLog(@"6 - %p", &blockVar); // 6 - 0x100500eb8
        };

        // 此时 blockVar 已被拷贝至堆上
        NSLog(@"7 - %p", &blockVar); // 7 - 0x100500eb8

        // block1 在堆上
        NSLog(@"8 - %p", block1); // 8 - 0x100500bb0

        // 另一个栈上的 Block 捕获并执行
        ^{
            // 捕获的 blockVar 也在堆上
            NSLog(@"9 - %p", &blockVar); // 9 - 0x100500eb8
        }();

        // blockVar 再次被栈上 Block 捕获,此时仍在堆上
        NSLog(@"10 - %p", &blockVar); // 10 - 0x100500eb8

        block1();

        // blockVar 此时仍在堆上
        NSLog(@"11 - %p", &blockVar); // 11 - 0x100701f18

        // 第二个栈上的 Block 被强指针引用,拷贝至堆上
        block2 = ^{
            // 捕获的 blockVar 也在堆上
            NSLog(@"12 - %p", &blockVar); // 12 - 0x100701f18
        };

        // blockVar 再次被堆上 Block 捕获,此时仍在堆上
        NSLog(@"13 - %p", &blockVar); // 13 - 0x100701f18
    }

    NSLog(@"14 - %p", block1); // 14 - 0x100500bb0
    NSLog(@"15 - %p", block2); // 15 - 0x100500940

    block1();
    block2();

    return 0;
}

将上述翻译为 C/C++ 代码:

static void __main_18_block_func_0(struct __main_18_block_impl_0 *__cself) {
  __Block_byref_blockVar_2 *blockVar = __cself->blockVar; // bound by ref

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_ed7da6_mi_58, &(blockVar->__forwarding->blockVar));
}

static void __main_18_block_func_1(struct __main_18_block_impl_1 *__cself) {
  __Block_byref_blockVar_2 *blockVar = __cself->blockVar; // bound by ref

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_ed7da6_mi_60, &(blockVar->__forwarding->blockVar));
}

static void __main_18_block_func_2(struct __main_18_block_impl_2 *__cself) {
  __Block_byref_blockVar_2 *blockVar = __cself->blockVar; // bound by ref

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_ed7da6_mi_62, &(blockVar->__forwarding->blockVar));
}

int main_18(int argc, const char * argv[]) {
    void(*block1)(void);
    void(*block2)(void);

    {
        __attribute__((__blocks__(byref))) __Block_byref_blockVar_2 blockVar = {(void*)0,(__Block_byref_blockVar_2 *)&blockVar, 0, sizeof(__Block_byref_blockVar_2), 10};

        // blockVar.__forwarding->blockVar
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_ed7da6_mi_56, &(blockVar.__forwarding->blockVar));


        NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_ed7da6_mi_57, ((void (*)())&__main_18_block_impl_0((void *)__main_18_block_func_0, &__main_18_block_desc_0_DATA, (__Block_byref_blockVar_2 *)&blockVar, 570425344)));

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_ed7da6_mi_59, &(blockVar.__forwarding->blockVar));


        ((void (*)())&__main_18_block_impl_1((void *)__main_18_block_func_1, &__main_18_block_desc_1_DATA, (__Block_byref_blockVar_2 *)&blockVar, 570425344))();


        NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_ed7da6_mi_61, &(blockVar.__forwarding->blockVar));


        block1 = ((void (*)())&__main_18_block_impl_2((void *)__main_18_block_func_2, &__main_18_block_desc_2_DATA, (__Block_byref_blockVar_2 *)&blockVar, 570425344));


        NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_ed7da6_mi_63, &(blockVar.__forwarding->blockVar));


        NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_ed7da6_mi_64, block1);


        ((void (*)())&__main_18_block_impl_3((void *)__main_18_block_func_3, &__main_18_block_desc_3_DATA, (__Block_byref_blockVar_2 *)&blockVar, 570425344))();


        NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_ed7da6_mi_66, &(blockVar.__forwarding->blockVar));

        ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);


        NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_ed7da6_mi_67, &(blockVar.__forwarding->blockVar));


        block2 = ((void (*)())&__main_18_block_impl_4((void *)__main_18_block_func_4, &__main_18_block_desc_4_DATA, (__Block_byref_blockVar_2 *)&blockVar, 570425344));


        NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_ed7da6_mi_69, &(blockVar.__forwarding->blockVar));
    }

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_ed7da6_mi_70, block1);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_ed7da6_mi_71, block2);

    ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
    ((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);

    return 0;
}

由上我们可以看出,__block 变量从声明后,无论在 Block 内外去访问均是通过结构体的 __forwarding 指针即 blockVar.__forwarding->blockVar。当 __block 变量在栈上时,blockVar.__forwarding->blockVar 就等同于直接通过 blockVar->blockVar 来访问的,因为此时 __forwarding 就指向栈上的结构体本身;而当 Block 拷贝到堆上时,__block 变量也会被拷贝到堆上,此时栈上的 __forwarding 将替换为指向堆上的结构体,而堆上的结构体里的 __forwarding 将指向堆上的结构体本身,从而保证后续的数据变动均是在堆上。

因此 __forwarding 存在的意义是无论 __block 配置在栈上还是堆上都能正确地访问 __block 变量。

内存管理

对于对象类型的 __block 变量自然也会更多涉及到内存管理方面:

@interface Foo_7 : NSObject
@property (nonatomic, assign) int bar;
@end

@implementation Foo_7
@end

int main_19(int argc, const char * argv[]) {
    __block Foo_7 *strongF = [[Foo_7 alloc] init];
    strongF.bar = 10;

    __block __weak Foo_7 *weakF = strongF;

    void(^block)(void) = ^{
        NSLog(@"%lu", (unsigned long)weakF.bar);
        NSLog(@"%lu", (unsigned long)strongF.bar);

        strongF = [Foo_7 new];
        strongF.bar = 100;
        NSLog(@"%lu", (unsigned long)strongF.bar);
    };

    block();

    return 0;
}

// OUTPUT:
// 10
// 10
// 100

将上述翻译为 C/C++ 代码:

struct __Block_byref_strongF_3 {
  void *__isa;
__Block_byref_strongF_3 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*); // 内存管理域
 void (*__Block_byref_id_object_dispose)(void*);     // 内存管理域
 Foo_7 *__strong strongF; // 根据外界强引用而强引用
};

struct __Block_byref_weakF_4 {
  void *__isa;
__Block_byref_weakF_4 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*); // 内存管理域
 void (*__Block_byref_id_object_dispose)(void*);     // 内存管理域
 Foo_7 *__weak weakF; // 根据外界弱引用而弱引用
};

struct __main_19_block_impl_0 {
  struct __block_impl impl;
  struct __main_19_block_desc_0* Desc;
  // 引用 __block 变量的结构体,但这里均为强指针,不受外界改变
  __Block_byref_weakF_4 *weakF; // by ref
  __Block_byref_strongF_3 *strongF; // by ref
  __main_19_block_impl_0(void *fp, struct __main_19_block_desc_0 *desc, __Block_byref_weakF_4 *_weakF, __Block_byref_strongF_3 *_strongF, int flags=0) : weakF(_weakF->__forwarding), strongF(_strongF->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_19_block_func_0(struct __main_19_block_impl_0 *__cself) {
  __Block_byref_weakF_4 *weakF = __cself->weakF; // bound by ref
  __Block_byref_strongF_3 *strongF = __cself->strongF; // bound by ref

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_ee0da7_mi_72, (unsigned long)((int (*)(id, SEL))(void *)objc_msgSend)((id)(weakF->__forwarding->weakF), sel_registerName("bar")));
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_ee0da7_mi_73, (unsigned long)((int (*)(id, SEL))(void *)objc_msgSend)((id)(strongF->__forwarding->strongF), sel_registerName("bar")));

  (strongF->__forwarding->strongF) = ((Foo_7 *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Foo_7"), sel_registerName("new"));
  ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)(strongF->__forwarding->strongF), sel_registerName("setBar:"), 100);
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_ps_0m9gnvtj0893vpf1cr595djh0000gn_T_main_ee0da7_mi_74, (unsigned long)((int (*)(id, SEL))(void *)objc_msgSend)((id)(strongF->__forwarding->strongF), sel_registerName("bar")));
}

// 栈上的 Block 拷贝到堆时,会调用 __main_19_block_copy_0 方法
static void __main_19_block_copy_0(struct __main_19_block_impl_0*dst, struct __main_19_block_impl_0*src) {
    // _Block_object_assign 将对 strongF & weakF 形成强引用(Retain)
    // BLOCK_FIELD_IS_BYREF flag 对应为 _Block_byref_copy
    _Block_object_assign((void*)&dst->weakF, (void*)src->weakF, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_assign((void*)&dst->strongF, (void*)src->strongF, 8/*BLOCK_FIELD_IS_BYREF*/);
}

// 堆上的 Block 销毁时,会调用 __main_19_block_dispose_0 方法
static void __main_19_block_dispose_0(struct __main_19_block_impl_0*src) {
    // _Block_object_dispose 将释放 strongF & weakF (Release)
    // BLOCK_FIELD_IS_BYREF flag 对应为 _Block_byref_release
    _Block_object_dispose((void*)src->weakF, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_dispose((void*)src->strongF, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static struct __main_19_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_19_block_impl_0*, struct __main_19_block_impl_0*);
  void (*dispose)(struct __main_19_block_impl_0*);
} __main_19_block_desc_0_DATA = { 0, sizeof(struct __main_19_block_impl_0), __main_19_block_copy_0, __main_19_block_dispose_0};

int main_19(int argc, const char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_strongF_3 strongF = {(void*)0,(__Block_byref_strongF_3 *)&strongF, 33554432, sizeof(__Block_byref_strongF_3), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((Foo_7 *(*)(id, SEL))(void *)objc_msgSend)((id)((Foo_7 *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Foo_7"), sel_registerName("alloc")), sel_registerName("init"))};
    ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)(strongF.__forwarding->strongF), sel_registerName("setBar:"), 10);

    __attribute__((__blocks__(byref))) __attribute__((objc_ownership(weak))) __Block_byref_weakF_4 weakF = {(void*)0,(__Block_byref_weakF_4 *)&weakF, 33554432, sizeof(__Block_byref_weakF_4), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, (strongF.__forwarding->strongF)};

    void(*block)(void) = ((void (*)())&__main_19_block_impl_0((void *)__main_19_block_func_0, &__main_19_block_desc_0_DATA, (__Block_byref_weakF_4 *)&weakF, (__Block_byref_strongF_3 *)&strongF, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

由此可以发现这与 Block 中捕获对象类型的自动变量时的内存管理类似,不同的是 __block 结构体指针在 Block 内部默认是强引用,因此 _Block_object_assign 中会进行强引用(Retain)的操作;而且 _Block_object_assign & _Block_object_dispose 方法的最后一位参数的不同(对象:BLOCK_FIELD_IS_OBJECT__blockBLOCK_FIELD_IS_BYREF),用来函数内部区分两者。

int main_20(int argc, const char * argv[]) {
    void(^block_1)(void);
    {
        NSMutableArray *arr = [[NSMutableArray alloc] init];
        // 弱引用
        __weak NSMutableArray *weakArr = arr;

        block_1 = ^(){
            NSLog(@"%@", weakArr);
        };

        block_1();
    }

    block_1();

    {
        NSMutableArray *arr = [[NSMutableArray alloc] init];
        // __block 下弱引用
        __block __weak NSMutableArray * weakArr = arr;

        block_1 = ^(){
            NSLog(@"%@", weakArr);
        };

        block_1();
    }

    block_1();

    return 0;
}

// OUTPUT:
// ()
// (null)
// ()
// (null)

这一例中,默认强指针引用的 arr 会在作用域(代码块)结束时被释放,因此 __weak 的弱引用指针将指向 nil__unsafe_unretained 则不会自动清空)。代码可以正常执行,但弱引用处的对象便无法输出,即使带有 __block,其结构体中弱指针指向的对象也会被清空而无法输出。

@interface Foo_8 : NSObject
@end

@implementation Foo_8
- (void)dealloc
{
#if !__has_feature(objc_arc)
    [super dealloc];
#endif
    NSLog(@"dealloc");
}
@end

int main_21(int argc, const char * argv[]) {
#if !__has_feature(objc_arc)
    __block Foo_8 *f = [[Foo_8 alloc] init];

    void(^block)(void) = [^{
        // 若不注释 [f release]; 一行则会崩溃
        // CRASH: EXC_BAD_INSTRUCTION
        NSLog(@"%@", f);

        f = [Foo_8 new];

        NSLog(@"%@", f);
    } copy];

    // [f release]; // dealloc

    block();

    [block release];
#endif
    return 0;
}

MRC 下需要注意的一点是,如上代码我们可能会以为 block 会对外界 f 进行持有,进而引用计数增加,所以在 block 声明后对 f 进行 release,最终在 block 执行后对其本身 releasef 在此时释放,调用 dealloc。然而此时却在 block 执行时发生了崩溃,这是因为 MRC 下的 __Block_byref_xxx 对于变量并不会进行持有操作,这也可被用来破解循环引用。

循环引用

我们知道,Obj-C 通过引用计数来管理对象的内存,但其中的一个问题便是循环引用,即两个对象均以强引用互相指向对方,此时两个对象都将无法释放,导致内存泄漏:

typedef void(^BlockType_5)(void);

@interface Foo_9 : NSObject
@property (nonatomic, assign) NSUInteger bar;
@property (nonatomic, copy) BlockType_5 block;
@end

@implementation Foo_9
- (void)dealloc
{
#if !__has_feature(objc_arc)
    [super dealloc];
#endif
    NSLog(@"dealloc");
}

- (void)foo_1 {
    // Block 捕获了 self,其强引用了 Block,导致双方都无法释放
    self.block = ^{
        // WARNING: Capturing 'self' strongly in this block is likely to lead to a retain cycle
        NSLog(@"%lu", (unsigned long)self.bar);
        // WARNING: Block implicitly retains 'self'; explicitly mention 'self' to indicate this is intended behavior
        NSLog(@"%lu", (unsigned long)_bar); // self->_bar
    };
}
@end

int main_22(int argc, const char * argv[]) {
#if __has_feature(objc_arc)
    Foo_9 *f = [[Foo_9 alloc] init];
    f.bar = 20;

    f.block = ^{
        // Block 捕获了 f,其强引用了 Block,导致双方都无法释放
        // WARNING: Capturing 'f' strongly in this block is likely to lead to a retain cycle
        NSLog(@"%lu", (unsigned long)f.bar);
    };

    f.block();
    [f foo_1];

    // Never call dealloc
#endif
    return 0;
}

// OUTPUT:
// 20

5

ARC

__weak & __unsafe_unretained

想要打破循环引用,我们需要一个不增加引用计数的指向。那么到底更改哪个引用呢?我们仍以上图为例:

  • 1⃣️ 处的强引用是我们在初始化时 Foo_9 *f = [[Foo_9 alloc] init]; 所赋予的,栈上的 f 指针存储了堆上的对象的内存地址;
  • 2⃣️ 处的强引用是 Block 结构体(__main_22_block_impl_0)对自身捕获到内部的对象的强引用,其引用是根据外界即 1⃣️ 处声明时的强弱来决定的;
  • 3⃣️ 处的强引用是根据我们在 Foo_9 类中声明的属性修饰 @property (nonatomic, copy) BlockType_5 block; 所决定的。

而我们需要 Block 应当随 Foo_9 对象销毁而销毁,如果将 3⃣️ 处改为弱引用或 __unsafe_unretained,则可能出现 Block 的提前释放。因此综上,我们可以将 1⃣️ 的引用改为弱引用或 __unsafe_unretained

// Foo_9

- (void)foo_2 {
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        NSLog(@"%lu", (unsigned long)weakSelf.bar);

        // 需要 __strong 避免编译器报错(也保证在下面使用时 self 没有被释放)
        __strong typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"%lu", (unsigned long)strongSelf->_bar);
    };
}

int main_23(int argc, const char * argv[]) {
#if __has_feature(objc_arc)
    Foo_9 *f = [[Foo_9 alloc] init];
    f.bar = 30;

    // 也可使用 typeof() 简化类型声明
    // __weak typeof(f) weakF = f;
    __weak Foo_9 *weakF = f;

    // __unsafe_unretained Foo_9 *unsafeUnretainedF = f;
    __unsafe_unretained typeof(f) unsafeUnretainedF = f;

    f.block = ^{
        NSLog(@"%lu", (unsigned long)weakF.bar);
        NSLog(@"%lu", (unsigned long)unsafeUnretainedF.bar);
    };

    f.block();
    [f foo_2];
#endif

    return 0;
}

// OUTPUT:
// 30
// 30
// dealloc

__weak & __unsafe_unretained 的区别在于前者在指向的对象销毁时,指针将自动置为 nil(Autoniling),而后者将保留指向的内存地址。

__block

__block 变量由于可以在 Block 内直接更改,因此其也可以用来解除循环引用:

int main_24(int argc, const char * argv[]) {
#if __has_feature(objc_arc)
    __block Foo_9 *f = [[Foo_9 alloc] init];
    f.bar = 30;

    f.block = ^{
        NSLog(@"%@", f);

        f = nil; // 将 __Block_byref 中的 f 置为 nil,打破循环
    };

    f.block();

    // CRASH: EXC_BAD_ACCESS
    // f.block();
#endif

    return 0;
}

// OUTPUT:
// <Foo_9: 0x1035005a0>
// dealloc

6

但需要注意:

  1. Block 必须执行,才可以将强引用的自动变量置为 nil,从而破解循环引用;
  2. 循环引用破解后,Block 和 f 均释放,因此如果后续再次执行 Block 将发生 EXC_BAD_ACCESS 崩溃。

MRC

MRC 下,我们需要手动将栈上的 Block 拷贝到堆上,并在结束使用时 release,但循环引用出现时即使手动释放对象也无法销毁:

int main_25(int argc, const char * argv[]) {
#if !__has_feature(objc_arc)
    Foo_9 *f = [[Foo_9 alloc] init];

    f.block = [^{
        NSLog(@"%@", f);
    } copy];

    f.block();

    [f release]; // Never call dealloc
#endif

    return 0;
}

// OUTPUT:
// <Foo_9: 0x1006bab90>

由于 MRC 没有强弱引用的概念,因从破解循环引用只能使用 __unsafe_unretained,其使得 Block 内部不再会对捕获的对象持有:

int main_26(int argc, const char * argv[]) {
#if !__has_feature(objc_arc)
    __unsafe_unretained Foo_9 *f = [[Foo_9 alloc] init];

    f.block = [^{
        NSLog(@"%@", f);
    } copy];

    f.block();

    [f release]; // Never call dealloc
#endif

    return 0;
}

// OUTPUT:
// <Foo_9: 0x100507320>
// dealloc

__block 其实也可以用来破解 MRC 下的循环引用,因为 __block 修饰的变量在 MRC 下,__Block_byref_xxx 将不会对捕获的变量持有:

int main_27(int argc, const char * argv[]) {
#if !__has_feature(objc_arc)
    __block Foo_9 *f = [[Foo_9 alloc] init];

    f.block = [^{
        NSLog(@"%@", f);
    } copy];

    f.block();

    [f release];
#endif

    return 0;
}

// OUTPUT:
// <Foo_9: 0x100532c60>
// dealloc

@weakify & @strongify

Reference