什么是 Block ?
Block 是苹果在 iOS4
添加的特性。它是一个带自动变量(局部变量)的匿名函数,同时也是 OC 对象类型
,所以可以把 Block 赋值给一个变量,也可以存储在 NSArray
NSDictionary
这样的容器中,或者作为函数返回值。Block 等同于其他语言中的 closure
lambda
。 Block 使用简单方便,在很多场景下可以替代 delegate。Block 在系统提供的 API 中也是随处可见。
Block 的语法
下面是一个完整的 Block 定义规则,Block 标志性的标识是 ^
(caret 脱字符号),这是每个 Block 必须拥有的。剩下的和匿名函数相同。
^ 返回值类型 (参数列表) {表达式};
^ int(int v1, int v2) {return v1 + v2;};
如果返回值类型为 void
, 没有参数,这些都是可以省略,下面最简模式的 Block:
^{表达式};
^{printf("hello world!");};
Block 也是 OC 对象类型
可以把 Block 赋值给变量或类属性。也可以通过 typedef
去简化定义 Block 类型。
typedef void(^blk_t)(); // 用 typedef 定义 Block 类型
void(^block1)() = ^{printf("简化前");};
blk_t block2 = ^{printf("简化后");};
Block 的使用规则
捕获变量
Block 一个很大的优点就是可以捕获外部变量在 Block 内使用,并且除了特定情况,只要 Block 存在这个被捕获的变量就能够一直使用。 这个规则对 局部变量
静态变量
全局变量
静态全局变量
都有效。但是其中的 局部变量
不能够在 Block 中被重新赋值。可以对 局部变量
加上 __block
说明符去解决这个问题。 下面举一个栗子来佐证刚才的说法,以下代码基于 ARC
:
typedef void(^blk_t)();
static int static_global_val = 1; // 静态全局变量(C 基础类型
static id static_global_obj; // 静态全局变量(OC 对象类型
int global_val = 1; // 全局变量(C 基础类型
id global_obj; // 全局变量(OC 对象类型
@interface TObject : NSObject
@property (nonatomic) blk_t block;
@end
@implementation TObject
- (instancetype)init {
self = [super init];
int automatic_val = 1; // 自动变量(C 基础类型
id automatic_obj = [[NSObject alloc] init]; // 自动变量(OC 对象类型
static int static_val = 1; // 静态变量(C 基础类型
static id static_obj; // 静态变量(OC 对象类型
static_global_obj = [NSObject new];
global_obj = [NSObject new];
static_obj = [NSObject new];
self.block = ^{
NSLog(@"static_global_val: %d", static_global_val);
NSLog(@"static_global_obj: %@", static_global_obj);
NSLog(@"global_val: %d", global_val);
NSLog(@"global_obj: %@", global_obj);
NSLog(@"static_val: %d", static_val);
NSLog(@"static_obj: %@", static_obj);
NSLog(@"automatic_val: %d", automatic_val);
NSLog(@"automatic_obj: %@", automatic_obj);
static_global_val = 0;
static_global_obj = [NSArray array];
global_val = 0;
global_obj = [NSArray array];
static_val = 0;
static_obj = [NSArray array];
// automatic_val = 0;
// automatic_obj = [NSArray array];
};
return self;
}
@end
int main(int argc, const char * argv[]) {
TObject *obj = [[TObject alloc] init];
obj.block();
return 0;
}
上面的例子定义了各种各样的变量并在 block 中使用它们,通过观察他们的表现来佐证我们的观点。在 Block 中注释的两行代码试图去更改 C 对象类型
和 OC 对象类型
的自动变量,但是并没有成功。这里编译器均会报错: Variable is not assignable (missing __block type specifier)
,编译器告诉我们这两个变量不能被赋值,可以通过加上 __block
说明符去解决这个问题。这刚好验证了上面的说法。 下面的代码块里的内容是上面代码执行后的输出。虽然在 main 函数中执行 block 时,自动变量 automatic_val
automatic_obj
已经超出了其所在的函数作用域,但是仍然能打印出里面的值。这点也是符合预期的。
2017-03-26 20:15:34.264803 BlockDemo static_global_val: 1
2017-03-26 20:15:34.265551 BlockDemo static_global_obj: <NSObject: 0x100202e00>
2017-03-26 20:15:34.265579 BlockDemo global_val: 1
2017-03-26 20:15:34.265724 BlockDemo global_obj: <NSObject: 0x1002000c0>
2017-03-26 20:15:34.265773 BlockDemo static_val: 1
2017-03-26 20:15:34.265825 BlockDemo static_obj: <NSObject: 0x100203b00>
2017-03-26 20:15:34.265860 BlockDemo automatic_val: 1
2017-03-26 20:15:34.265927 BlockDemo automatic_obj: <NSObject: 0x1002024b0>
正确的储存 Block
文章的开头我们就讲到了 Block 是一个 OC 对象, 可以把它赋值给一个变量存储起来。但是这里 Block 和普通OC对象还是有一点细小的区别的,操作不当有可能 Block 就会被提前释放掉。
MRC
下要储存定义在函数内并且截获了自动变量的 Block 时。 如果期望它能超出函数作用域之外,需要先对 Block 进行 copy
操作,然后把返回的结果赋值给变量。或者赋值给 Property
时需要把它的 attribute
设置为 copy
,例如: @property (copy) blk_t block;
。 此时就和管理普通对象的内存无异了。可以对其 retain
release
。
ARC
下则完全和普通对象一样,使用 __strong
的修饰符的变量就好。不需要像 MRC
下去做 copy
操作
避免循环引用
上面已经展示过 Block 可以捕获自动变量,并且可以让其超过它自身所在的函数作用域而存在。Block 能有这个功能只是因为它持有了这个变量,这个变量只要 Block 存在它就会存在。但是这样会有一个安全隐患—会产生循环引用。例如下面的例子:
/// 运行在 ARC 下:
typedef void(^blk_t)();
@interface TObject : NSObject
@property (nonatomic, copy) blk_t block;
@end
@implementation TObject
- (instancetype)init {
self = [super init];
self.block = ^{ NSLog(@"%@", self); };
return self;
}
@end
上面你的代码,self 持有 block,但 block 也持有了 self。所以就循环引用了,谁也释放不了谁,造成内存泄漏。解决办法如下:
- (instancetype)init {
self = [super init];
__block __typeof(self) weakSelf = self; // MRC 的情况下
__weak __typeof(self) weakSelf = self; // ARC 的情况下
self.block = ^{ NSLog(@"%@", weakSelf);};
return self;
}
在 MRC
下使用 __block
说明符去避免循环引用
在 ARC
下使用 __weak
修饰符去避免循环应用
这两种方法都能在对应的内存管理机制下,让 Block 不 retain
或 强持有 截获的 self。 因为 self 持有 block。 所以也不用担心 block 执行时 self 会被释放。这就解决 Block 循环引用的问题。