Swift Tips - Defer关键字

前面有说到,在 swift 2.0 引入了 guard 关键字,可以让代码编写更流畅。它的优雅简洁而功能强大确实给了我们极大的方便。具体可以参见 这里

而,跟 guard 一同引入的还有一个关键字 —— defer

defer

一句话总结 defer 就是:让执行推迟。

我们知道,在错误处理方面,guard 和新的 throw 语法之间,Swift 鼓励用尽早返回错误(这也是我最喜欢的方式)来代替嵌套 if 的处理方式。尽早返回让处理更清晰了,但是已经被初始化(可能也正在被使用)的资源必须在返回前被处理干净。

defer关键字为此提供了安全又简单的处理方式:声明一个 block,当前代码执行的闭包退出时会执行该 block

具体我们来看下面这个栗子:

举个栗子

import Darwin
​
func currentHostName() -> String {
 let capacity = Int(NI_MAXHOST)
 let buffer = UnsafeMutablePointer<Int8>.allocate(capacity: capacity)
​
 //调用 gethostname(2) 的函数,用来返回当前系统的主机名称
 guard gethostname(buffer, capacity) == 0 else {
 buffer.deallocate()
 return "localhost"
 }
​
 let hostname = String(cString: buffer)
 buffer.deallocate()
​
 return hostname
}

这里有一个在最开始就创建的 UnsafeMutablePointer<UInt8> 用于存储目标数据,但是我既要在错误发生后销毁它,又要在正常流程下不再使用它时对其进行销毁。

这种设计很容易导致错误,而且不停地在做重复工作。

通过使用 defer 语句,我们可以排除潜在的错误并且简化代码:

func currentHostName() -> String {
 let capacity = Int(NI_MAXHOST)
 let buffer = UnsafeMutablePointer<Int8>.allocate(capacity: capacity)

 defer { buffer.deallocate() }
​
 guard gethostname(buffer, capacity) == 0 else {
 return "localhost"
 }
​
 return String(cString: buffer)
}

尽管 defer 紧接着出现在 allocate(capacity:) 调用之后,但它要等到当前区域结束时才会被执行。多亏了 deferbuffer 才能无论在哪个点退出函数都可以被释放。

考虑在任何需要配对调用的 API 上都使用 defer,比如 allocate(capacity:) / deallocate()wait() / signal()open() / close()。这样的话,你就可以消除一种程序员易犯的错误。

多个 defer

如果在同一个作用域内使用多个 defer 语句,它们会根据出现顺序反过来执行——像栈一样。这个反序是非常重要的细节,保证了被延迟的代码块创建时作用域内存在的东西,在代码块执行同样存在。

举个例子,执行这段代码会得到下面的输出:

func procrastinate() {
 defer { print("wash the dishes") }
 defer { print("take out the recycling") }
 defer { print("clean the refrigerator") }
​
 print("play videogames")
}
/*  输出结果:
 play videogames
 clean the refrigerator
 take out the recycling
 wash the dishes
*/

那么问题来了,如果你像这样嵌套 defer 语句,会怎么样?

defer { defer { print("clean the gutter") } }

哈哈,当然你可以亲自试一下。

defer 中的变量

如果在 defer 语句中引用了一个变量,执行时会用到变量最终的值。换句话说:defer 代码块不会捕获变量当前的值。

如果你运行这段代码,你会得到下面的输出:

func flipFlop() {
 var position = "It's pronounced /ɡɪf/"
 defer { print(position) }
​
 position = "It's pronounced /dʒɪf/"
 defer { print(position) }
}
/*  输出结果:
 It's pronounced /dʒɪf/
 It's pronounced /dʒɪf/
*/

defer 跳不出作用域

需要注意的是, defer 代码块无法跳出它所在的作用域。因此如你尝试调用一个会 throw 的方法,抛出的错误就无法传递到其周围的上下文。

func burnAfterReading(file url: URL) throws {
 defer { try FileManager.default.removeItem(at: url) }
 // 🛑 Errors not handled
​
 let string = try String(contentsOf: url)
}

作为替代,你可以使用 try? 来无视掉错误,或者直接将语句移出 defer 代码块,将其放到函数的最后,正常的执行。

defer 并不是总是会执行

看看下面这个栗子:

func foo() {
 guard false else { return }
 defer {
 print("finally")
 }
}

显然,上面永远不会执行到 defer 这一句,这时候就不会被执行。

这个故事告诉我们,至少要执行到 defer 这一行,它才保证后面会触发。

defer 的坏处

虽然 defer 像一个语法糖一样,但也要小心使用避免形成容易误解、难以阅读的代码。在某些情况下你可能会尝试用 defer 来对某些值返回之前做最后一步的处理,例如说在后置运算符 ++ 的实现中:

postfix func ++(inout x: Int) -> Int {
 let current = x
 x += 1
 return current
}

在这种情况下,可以用 defer 来进行一个很另类的操作。如果能在 defer 中处理的话为什么要创建临时变量呢?

postfix func ++(inout x: Int) -> Int {
 defer { x += 1 }
 return x
}

这种写法确实聪明,但这样却颠倒了函数的逻辑顺序,极大降低了代码的可读性。应该严格遵循 defer在整个程序最后运行以释放已申请资源的原则,其他任何使用方法都可能让代码乱成一团。

一脸懵逼是不是?

写在最后:

「聪明的程序员明白自己的局限性」,我们必须权衡每种语言特性的好处和其成本。

类似于 guard 的新特性能让代码流程上更线性,可读性更高,就应该尽可能使用。

同样 defer 也解决了重要的问题,但是会强迫我们一定要找到它声明的地方才能追踪到其销毁的方法,因为声明方法很容易被滚动出了视野之外,所以应该尽可能遵循它出现的初衷尽可能少地使用,避免造成混淆和晦涩。

喜欢我的可以关注收藏我的个人博客:Ro.bber

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

推荐阅读更多精彩内容

  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 5,134评论 0 9
  • Swift官方文档的词汇结构中, 有非常多的关键字, 它们被用于声明中、语句中、表达式中、类中、模式中, 还有以数...
    小宇宙_fly阅读 745评论 0 3
  • 陈阵和杨克打算去掏狼崽。大概是在草原呆久了,知青对狼的认识也越发的深起来,胆子越来越大,想要做一些像蒙古人做的事,...
    煎鱼炒肉阅读 836评论 0 0
  • 一场说走就走的旅行从今天开始青海湖大环线7日游。早晨6点半,我们从兰州出发,去往西宁的高速上,向远处望去,映入眼帘...
    结雨堂李宏宇阅读 235评论 0 0
  • 9月20到达上海 接触了梵音瑜伽 到现在我感觉我得到了很大的改善。 我以前是很内向现在很开朗。 从培训到现在...
    奶糖爱吃大白兔阅读 939评论 0 1