Date | Notes | Demo | Source Code |
---|---|---|---|
2019-07-27 | 首次提交 | - | - |
2019-11-24 | 重新整理部分内容;补充《Effective Objective-C 2.0》&《Objective-C 高级编程》相关内容 | Block_in_Obj-C | objc4-756.2、libclosure-73 |
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 来设置:
⚠️ 注意:本文中若未指明 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_impl
的 isa
指向了 _NSConcreteStackBlock
类对象的地址,也印证了 Block 的本质是特殊的 Obj-C 对象;__block_impl
的 FuncPtr
函数指针则指向包含代码块的静态函数,在 Block 真正执行时,将通过 block->FuncPtr(block)
找到静态函数并调用。上述代码中的 block
结构正如下图:
变量捕获
自动变量
自动变量(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
,这些父类又都继承自 NSBlock
,NSBlock
又继承自 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 时其本身已经被系统回收,捕获的自动变量 c
和 f
也已经从栈上释放,因此访问时出现了脏数据,访问 Block 本身也出现了 EXC_BAD_ACCESS
崩溃。为了避免这种问题,我们需要将 Block 分配在堆上(即 __NSMallocBlock__
),这是因为堆区的内存由开发者自己管理,可以避免被自动回收。
ARC 下,__NSStackBlock__
类型的 Block 中捕获了 strongP
和 weakP
(可以通过为翻译 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
到堆上:- 强指针指向的 Block 会被自动拷贝;
- Block 作为函数返回值会被自动拷贝;
- Block 作为 Cocoa API 中方法名含有
usingBlock
的参数时会被自动拷贝; - Block 作为 GCD API 参数时会被自动拷贝。
- 当拷贝到堆上后,Block 将把
_NSConcreteMallocBlock
类对象写入到isa
中,即isa = &_NSConcreteMallocBlock
。
因此,Block 作为属性时应当使用 copy
或 strong
修饰(推荐 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 项目的汇编代码呢?
- 在 Block 内部打个断点,当运行至断点时,Xcode Menu - Debug - Debug Workflow - Always Show Disassembly,即可自动跳转至汇编视图;
- 使用 Mac 自带的
otool -tvV PRODUCT_PATH
命令即可将 Xcode 编译后的二进制产物(Products 目录下)的汇编指令打印出来。
这里我们暂时没必要搞清楚每一条汇编指令,而是着重看一下右侧的注释 objc_retainBlock
和 objc_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;
}
__block
将 autoVar
封装到 __Block_byref_autoVar_0
结构体中,将 autoArray
封装到 __Block_byref_autoArray_1
结构体中,其中的 isa
指针标志着该结构体本质也属于 Obj-C 对象;__forwarding
指针指向了该结构体本身,在初始化时被赋值为声明的结构体的地址;__size
为该结构体的大小信息;autoVar
和 autoArray
为捕获的变量本身。因此捕获了 __block
变量的 Block 结构如下图所示:
__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
,__block
:BLOCK_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
执行后对其本身 release
,f
在此时释放,调用 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
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
但需要注意:
- Block 必须执行,才可以将强引用的自动变量置为
nil
,从而破解循环引用; - 循环引用破解后,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