Objective-C Block Part1-介绍&使用

什么是 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 循环引用的问题。

    原文作者:f939f3106e72
    原文地址: https://www.jianshu.com/p/5b4d6a8e36f6
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞