Block(Closure) Tips

使用 Block 的时候谨记以下几点:
1.Block类型:全局块(Global Block)和堆块(Heap Block),以及栈块(Stack Block)。
2.变量捕获: 默认无法修改变量,需要添加 __block 修饰符
3.避免循环引用。

推荐文章:
1.官方文档:
快速上手:Working with Blocks,进阶:Blocks Programming Topics
2.优秀博客:
Deep into Block: A look inside blocks: Episode 1, Episode 2, Episode 3
底层实现:谈 Objective-C Block 的实现 - By 唐巧
类型探讨:Objective-C Blocks Quiz
啥都有:对 Objective-C 中 Block 的追探

类型

之前看文档或是其他人写的文章都讲述了三种 Block 类型,但直到看到上面的测试,我才意识 Block 类型是如何决定的。经过一些实践,简单来说,在 Objective-C 中:
1.Block 中没有使用外围变量的话,在开启 ARC 的条件下,因为不需要依赖其他状态,其使用的内存区域在编译期就可以确定,将会被编译为全局块;而没有开启 ARC 时,总是被编译为栈块。
在唐巧的博客中使用 Clang 来研究 Block ,这里也学习了一下,但是基本上这些块都是被编译为栈块,按照唐巧的说法,在 ARC 下,是被编译为全局块的。

《Effective Objective-C 2.0》给出如下的例子:声明了一个 Block,但需要根据条件来选择合适的实现。

void (^block)();
if(条件 A) {
    block = ^{
        NSlog(@"Block A");
    }
}else{
    block = ^{
       NSLog(@"Block B");
    }
}
block()

上面这段代码的问题在于,这两个块只在相应的 if 或 else 语句范围内有效,离开了相应的范围后,编译器有可能覆写块所在的内存。这样的代码可以编译,但在运行时可能出错。书中提出,这里可以使用 copy 操作将块拷贝到堆上,这样一来,块可以在定义它的范围外使用。正确写法如下:

void (^block)();
if(条件 A) {
    block = [^{
        NSlog(@"Block A");
    } copy];
}else{
    block = [^{
       NSLog(@"Block B");
    } copy];
}
block()

书中没有提及此处是否开启了 ARC;我将上述代码编写在 C 文件中,使用 Clang 将之转化为 cpp 实现后,发现这里是个栈块;而按照上一条的说法,开启 ARC 后,这里将会被编译为全局块。那么在开启 ARC 的条件下,这段代码是否有问题呢?答案是没有,因为原来的代码的问题在于块的内存在栈中,而开启 ARC 下,块编译为全局块,不存在这个问题。
Objective-C Blocks Quiz 的 Example C中,提到:

That’s correct. Since the block doesn’t capture any variables in its closure, it doesn’t need any state set up at runtime. it gets compiled as an NSGlobalBlock. It’s neither on the stack nor the heap, but part of the code segment, like any C function. This works both with and without ARC.

2.使用了外围变量的话,若开启了 ARC,则只会被编译为堆块,内存是分配在堆上的;若没有开启 ARC,函数中定义的 Block 将会编译为栈块(如今除了旧项目很少有不开启 ARC 的吧)。

总结下:开启 ARC 的条件下,将不会有栈块,这样可以省去不少麻烦,但是 Objective-C Blocks Quiz 的最后也提到LLVM的一位维护者说:

We consider this to be a compiler bug, and it has been fixed for months in the open-source clang repository. What that means for any hypothetical future Xcode release, I cannot say. :)

保险点,开启 ARC。

生命周期

栈块,顾名思义,离开了定义它的函数范围就被收回了;堆块,就像普通的对象一样采用引用计数机制;全局块,在应用的整个生命周期都存在。

变量捕获 + 循环引用

在声明块的范围内,所有变量都可以被块捕获(就是可以使用)。默认情况下,不可以在块里修改外围的变量,因为块拷贝了一份变量到它的内存中,对于对象则是拷贝了对象的地址;若想修改,需要在外围变量前面添加修饰符 __block。是否添加 __block 修饰符,源代码会有很大不同,可以在唐巧的博客里看到。另外,Block 最著名的问题就是循环引用,就是由于互相保持着对方的引用,所以 ARC 拿这俩没办法,将会一直存在;复杂一点的,多个对象对其他对象的引用形成了一个圈,ARC 也是没办法。一般的解决办法是,在形成的引用圈的一处使用弱引用,这样就有机会打破强引用圈。

常见的循环引用陷阱是在类中定义了块变量,然后在块中使用了类实例的属性。而在访问属性的同时,块实质上隐式地捕获了当前实例,这样一来就造成了循环引用。
在 Objective-C 中,解决方法通常如下:

__weak myClass *weakSelf = self;
self.block = ^{
    __strong myClass *strongSelf = weakSelf;//防止当前 self 为空
    if(strongSelf){
        [strongSelf doSomeThing];
    }
} 

又或者使用 typeof,不过这种高级技巧,可读性就不敢保证了。今天这条微博引起了一些讨论:

是否会捕获 self

这个代码的关键在于 typeof(self),因为它在块的内部出现,那么块是否捕获了 self 呢?答案是不会,因为 typeof是个编译符号,在编译期间起作用,而不是运行时,因此这么写不会造成循环引用的问题。

在 Swift 中,针对这个问题有了更加优雅的解决方案:捕获列表(Capture List)。在闭包参数前添加列表,从属关系以及捕获对象成对为一组值,多组值用「,」隔开。
无参数:

var someClosure: () -> Void = {
    [unowned self, weak delegate = self.delegate!] in
    //closure body
}

有参数:

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

推荐阅读更多精彩内容

  • 2.1 Blcoks概要 2.1.1 什么是Blocks Blocks是C语言的扩充功能——“带有自动变量(即局部...
    SkyMing一C阅读 2,320评论 6 18
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,123评论 29 470
  • Blocks编程要点 目录 简介............................................
    xuejunjun阅读 1,198评论 0 5
  • 一、Objective-C发展史 Objective-C从1983年诞生,已经走过了30多年的历程。随着时间的推移...
    没事蹦蹦阅读 5,812评论 12 34
  • 前言 Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这...
    小人不才阅读 3,757评论 0 23