Block 是 Objective-C 语言创建闭包的方式,从 iOS4 开始 block 便在很多场景下替代了 delegate,相对来说 block 的最大优点就是可以使代码集中在一起,提高可读性,但也提高了代码调试的复杂度,并增加了循环引用产生的场景。

一些概念

Block 会使 app 运行成本增高,因为 delegate 只是保存了一个对象指针,而 block 本身实际上就是对象,它会被编译成 struct 并为其内容分配空间,struct 内容主要包括 isa 指针、block 对应实现函数的地址以及 block 复制过来的变量,这些内容也会随着需要从栈内存复制到堆内存。

  • isa:指向 block 对应的 Class

    • _NSConcreteGlobalBlock:定义在全局区的 block 会作为代码片段存在
    • _NSConcreteStackBlock:定义在方法中的 block 会保存在栈中,当函数返回时被销毁
    • _NSConcreteMallocBlock:为了增加 block 的生命周期,可以用 copy 方法将其复制到堆中,如果 block 已经在堆里了,再次进行 copy 只会增加引用计数
  • IMP:block 块中的代码会作为方法形式存在,IMP 指向方法地址

  • 复制的变量:block 能够读取它所在函数的内部变量,该变量会被复制到 struct 中,默认是值复制,不能修改,加上 __block 修饰的是引用复制,可以修改

简单例子

__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

循环引用

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

// 因为 self 会强引用 block,所以将 self 定义成 weak
__weak __typeof(self)weakSelf = self; 

AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
    // 如果 block 里有多处引用 weakSelf,则需要转成 strongSelf,避免 weakSelf 因多线程导致其被释放后出现问题
    __strong __typeof(weakSelf)strongSelf = weakSelf; 
    
    strongSelf.networkReachabilityStatus = status;
    
    if (strongSelf.networkReachabilityStatusBlock) {
        strongSelf.networkReachabilityStatusBlock(status);
    }
};

并不是所有的 block 都会造成循环引用,关键点是 block 对象本身和它内部使用的对象是否互相持有,下面场景就不需要声明 weakSelf

[UIView animateWithDuration:0.3f animations:^{
    self.frame = .zero
}];

作为属性使用的场景

将 block 定义成类型

typedef void (^SelectedItemHandler)(NSString *itemId);

定义相关属性,虽然 strong 和 copy 的实际效果一样,为了代码可读性还是建议用 copy

@property (nonatomic, copy) SelectedItemHandler selectedHandler;

定义方法用来接收 block 参数并设置 block 属性,如有多个参数,block 参数应为最后一个

- (void)setSelectedItemHandler:(SelectedItemHandler)selectedHandler {
    self.selectedHandler = selectedHandler;
}

调用上面定义的方法,并传入 SelectedItemHandler 类型的 block 代码块

[self.bannerView setSelectedItemHandler:^(NSString *itemId) {
    ...
}];

当发生相关事件时触发 block 属性中的代码块

if (self.selectedHandler) {
    self.selectedHandler(self.items[indexPath.item][@"itemId"]);
}