风行的博客

了解 Block

block - 其实就是 Objective-C 对于闭包的对象实现。

Block 是 Apple 为 C、C++ 以及 Objective-C 添加的特性,使得这些语言可以用类 lambda 表达式的语法来创建闭包。在适当的时候使用 block 替代 delegation 可以使代码看起来更紧凑,但也需要注意 block 带来的循环引用问题。

Block

block 实际上就是对象,它会被编译成 struct 并分配好空间,主要包括 isa 指针、block 对应实现函数的地址以及 block 复制过来的变量,在学习 block 的很多特性时,如果能以 “block是对象,块中代码是函数” 的思维去思考,很多问题就都会变得简单了。

  • isa:指向 block 对应的 Class
    • _NSConcreteGlobalBlock:定义在全局区的 block 会作为代码片段存在
    • _NSConcreteStackBlock:定义在方法中的 block 会保存在栈中,当函数返回时被销毁
    • _NSConcreteMallocBlock:为了增加 block 的生命周期,可以用 copy 方法将其复制到堆中,如果 block 已经在堆里了,再次进行 copy 只会增加引用计数
  • IMP:block 块中的代码会作为方法形式存在,IMP 指向方法地址
  • 复制的变量:block 能够读取它所在函数的内部变量,该变量会被复制到 struct 中,默认是值复制,不能够修改,用 __block 修饰的是引用复制,所以可以修改
1
2
3
4
5
6
7
8
9
10
__block NSInteger mutiplier = 7; // 用 __block 修饰后,在 block 里就可以修改 mutiplier 的值

// 定义名为 myBlock 的代码块,返回值类型为 NSInteger,并有一个名为 num 的 NSInteger 型参数
NSInteger (^myBlock)(NSInteger) = ^(NSInteger num){
    mutiplier = 3;

    return num * mutiplier;
};

NSLog(@"%ld", myBlock(3)); // 像调用函数一样使用 block


循环引用(retain cycle)

对象之间相互持有便会造成循环引用问题,下面用一个经典例子看一下怎么解决 block 中的循环引用问题

1
2
3
4
5
6
7
8
9
10
11
12
13
// 因为 self 会强引用 block,所以避免 block 强引用 self 对象
__weak __typeof(self)weakSelf = self;

AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
    // 如果有多处引用 weakSelf,则需要转成 strongSelf,避免 weakSelf 被释放后出现问题
    __strong __typeof(weakSelf)strongSelf = weakSelf;

    strongSelf.networkReachabilityStatus = status;

    if (strongSelf.networkReachabilityStatusBlock) {
        strongSelf.networkReachabilityStatusBlock(status);
    }
};


Block 作为属性使用

  • 将 block 定义成类型
1
typedef void (^BannerViewSelectedBlock)(NSString *bannerId);
  • 然后定义相关属性,虽然 strong 和 copy 的实际效果一样,为了代码可读性还是建议用 copy
1
@property (nonatomic, copy) BannerViewSelectedBlock seletedBlock;
  • 定义方法用来接收 block 参数并设置 block 属性,如有多个参数,block 参数应为最后一个
1
2
3
- (void)setCompletionBlockWithSeleted:(BannerViewSelectedBlock)completionBlock {
    self.seletedBlock = completionBlock;
}
  • 调用上面定义的方法,并传入 BannerViewSelectedBlock 类型的 block 代码块
1
2
3
[self.bannerView setCompletionBlockWithSeleted:^(NSString *bannerId) {
  ...
}];
  • 当发生相关事件时触发 block 属性中的代码块
1
2
3
if (self.seletedBlock) {
  self.seletedBlock(self.items[indexPath.item][@"bannerId"]);
}

参考代码