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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容