swift中defer几个简单的使用场景详解

前言

最近准备把 swift 文档再扫一遍,发现了 defer 这个关键字,defer 是个非常重要的 swift 语言特征,恕本人愚钝,以前还从来没有用过这个呢~ 简单地列一下这个东西有哪些可以用得上的情景吧~~话不多说了,来一起看看详细的介绍吧。

defer 是干什么用的

很简单,用一句话概括,就是 defer block 里的代码会在函数 return 之前执行,无论函数是从哪个分支 return 的,还是有 throw,还是自然而然走到最后一行。

这个关键字就跟 Java 里的 try-catch-finally 的 finally 一样,不管 try catch 走哪个分支,它都会在函数 return 之前执行。而且它比 Java 的 finally 还更强大的一点是,它可以独立于 try catch 存在,所以它也可以成为整理函数流程的一个小帮手。在函数 return 之前无论如何都要做的处理,可以放进这个 block 里,让代码看起来更干净一些~

下面是 swift 文档上的例子:

var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
func fridgeContains(_ food: String) -> Bool {
 fridgeIsOpen = true
 defer {
  fridgeIsOpen = false
 }
 let result = fridgeContent.contains(food)
 return result
}
fridgeContains("banana")
print(fridgeIsOpen)

这个例子里执行的顺序是,先 fridgeIsOpen = true ,然后是函数体正常的流程,最后在 return 之前执行 fridgeIsOpen = false 。

几个简单的使用场景

try catch 结构

最典型的场景,我想也是 defer 这个关键字诞生的主要原因吧:

func foo() {
 defer {
 print("finally")
 }
 do {
 throw NSError()
 print("impossible")
 } catch {
 print("handle error")
 }
}

不管 do block 是否 throw error,有没有 catch 到,还是 throw 出去了,都会保证在整个函数 return 前执行 defer 。在这个例子里,就是先 print 出 "handle error" 再 print 出 "finally"。

do block 里也可以写 defer :

do {
 defer {
 print("finally")
 }
 throw NSError()
 print("impossible")
} catch {
 print("handle error")
}

那么它执行的顺序就会是在 catch block 之前,也就是先 print 出 "finally" 再 print 出 "handle error"。

清理工作、回收资源

跟 swift 文档举的例子类似, defer 一个很适合的使用场景就是用来做清理工作。文件操作就是一个很好的例子:

关闭文件

func foo() {
 let fileDescriptor = open(url.path, O_EVTONLY)
 defer {
 close(fileDescriptor)
 }
 // use fileDescriptor...
}

这样就不怕哪个分支忘了写,或者中间 throw 个 error,导致 fileDescriptor 没法正常关闭。还有一些类似的场景:

dealloc 手动分配的空间

func foo() {
 let valuePointer = UnsafeMutablePointer<T>.allocate(capacity: 1)
 defer {
 valuePointer.deallocate(capacity: 1)
 }
 // use pointer...
}

加/解锁:下面是 swift 里类似 Objective-C 的 synchronized block 的一种写法,可以使用任何一个 NSObject 作 lock

func foo() {
 objc_sync_enter(lock)
 defer {
 objc_sync_exit(lock)
 }
 // do something...
}

像这种成对调用的方法,可以用 defer 把它们放在一起,一目了然。

调 completion block

这是一个让我感觉“如果当时知道 defer ”就好了的场景,就是有时候一个函数分支比较多,可能某个小分支 return 之前就忘了调 completion block,结果藏下一个不易发现的 bug。用 defer 就可以不用担心这个问题了:

func foo(completion: () -> Void) {
 defer {
 self.isLoading = false
 completion()
 }
 guard error == nil else { return }
 // handle success
}

有时候 completion 要根据情况传不同的参数,这时 defer 就不好使了。不过如果 completion block 被存下来了,我们还是可以用它来确保执行后能释放:

func foo() {
 defer {
 self.completion = nil
 }
 if (succeed) {
 self.completion(.success(result))
 } else {
 self.completion(.error(error))
 }
}

调 super 方法

有时候 override 一个方法,主要目的是在 super 方法之前做一些准备工作,比如 UICollectionViewLayout 的 prepare(forCollectionViewUpdates:) ,那么我们就可以把调用 super 的部分放在 defer 里:

func override foo() {
 defer {
 super.foo()
 }
 // some preparation before super.foo()...
}

一些细节

任意 scope 都可以有 defer

虽然大部分的使用场景是在函数里,不过理论上任何一个 { } 之间都是可以写 defer 的。比如一个普通的循环:

var sumOfOdd = 0
for i in 0...10 {
 defer {
 print("Look! It's \(i)")
 }
 if i % 2 == 0 {
 continue
 }
 sumOfOdd += i
}

continue 或者 break 都不会妨碍 defer 的执行。甚至一个平白无故的 closure 里也可以写 defer :

{
 defer { print("bye!") }
 print("hello!")
}

就是这样没什么意义就是了……

必须执行到 defer 才会触发
假设有这样一个问题:一个 scope 里的 defer 能保证一定会执行吗? 答案是否……比如下面这个例子:

func foo() throws {
 do {
 throw NSError()
 print("impossible")
 }
 defer {
 print("finally")
 }
}
try?foo()

不会执行 defer,不会 print 任何东西。这个故事告诉我们,至少要执行到 defer 这一行,它才保证后面会触发。同样道理,提前 return 也是一样不行的:

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

多个 defer

一个 scope 可以有多个 defer,顺序是像栈一样倒着执行的:每遇到一个 defer 就像压进一个栈里,到 scope 结束的时候,后进栈的先执行。如下面的代码,会按 1、2、3、4、5、6 的顺序 print 出来。

func foo() {
 print("1")
 defer {
 print("6")
 }
 print("2")
 defer {
 print("5")
 }
 print("3")
 defer {
 print("4")
 }
}

但是我强烈建议不要这么写。我是建议一个 scope 里不要有多个 defer,感觉除了让读代码的人感觉混乱之外没有什么好处。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Swift中defer的正确使用方法

    defer 是干什么用的 很简单,用一句话概括,就是 defer block 里的代码会在函数 return 之前执行,无论函数是从哪个分支 return 的,还是有 throw,还是自然而然走到最后一行. 这个关键字就跟 Java 里的 try-catch-finally 的finally一样,不管 try catch 走哪个分支,它都会在函数 return 之前执行.而且它比 Java 的finally还更强大的一点是,它可以独立于 try catch 存在,所以它也可以成为整理函数流程的一

  • Swift中defer关键字推迟执行示例详解

    前言 大家应该都知道,在一些语言中,有try/finally这样的控制语句,比如Java. 这种语句可以让我们在finally代码块中执行必须要执行的代码,不管之前怎样的兴风作浪. 在Swift 2.0中,Apple提供了defer关键字,让我们可以实现同样的效果. func checkSomething() { print("CheckPoint 1") doSomething() print("CheckPoint 4") } func doSomething(

  • swift中defer几个简单的使用场景详解

    前言 最近准备把 swift 文档再扫一遍,发现了 defer 这个关键字,defer 是个非常重要的 swift 语言特征,恕本人愚钝,以前还从来没有用过这个呢~ 简单地列一下这个东西有哪些可以用得上的情景吧~~话不多说了,来一起看看详细的介绍吧. defer 是干什么用的 很简单,用一句话概括,就是 defer block 里的代码会在函数 return 之前执行,无论函数是从哪个分支 return 的,还是有 throw,还是自然而然走到最后一行. 这个关键字就跟 Java 里的 try-

  • Swift中的高阶函数功能作用示例详解

    目录 高阶函数的作用 1. 简化代码 2. 提高可读性 3. 支持函数式编程 4. 提高代码的可重用性 常见的高阶函数 1. map() 2. filter() 3. reduce() 4. sorted() 5. forEach() 6. compactMap() 7. flatMap() 8. zip() 9. first() 10. contains() 高阶函数的作用 Swift中的高阶函数是指那些参数或返回值是函数的函数.它们的存在使得我们可以用非常简洁和优雅的代码来解决许多问题. 1

  • Swift中优雅处理闭包导致的循环引用详解

    前言 Objective-C 作为一门资历很老的语言,添加了 Block 这个特性后深受广大 iOS 开发者的喜爱.在 Swift 中,对应的概念叫做 Closure,即闭包.虽然更换了名字,但是概念和用法还是相似的,就算是副作用也一样,有可能导致循环引用. 下面我们用一个例子看一下,首先我们需要第一个控制器(FirstViewController),它所做的就是简单的推出第二个控制器(SecondViewController). class FirstViewController: UIVie

  • Swift中常量和变量的区别与声明详解

    Swift是弱类型语言吗? 答案是否定的,Swift 是强类型语言,下面上一个栗子 上面代码中报错了,报的是不能指定 Int 类型为 String 类型. 这里要注意一下在 Swift 中的整形是I,而字符类型首字母是S,都是大写字母 在 Swift 中我们可以直接声明 var 类型变量,可以不直接指定其类型,这是Swift语言的一种机制,当我们声明一个变量的初始值后,就已经确定这个变量是什么类型,Type Inference (类型推断) 如何查看一个变量的类型 在开发中我们一般如何查看一个变

  • swift中AnyObject和Any的介绍与区别详解

    诞生 swift 作为新起步的语言,必然抛不掉一些历史遗留包袱.用过 Objective-C 的同学肯定知道有一种叫做 id 的类型.他可以表示任意类的实例,编译器不会对其类型声明的变量进行检查.在用 swift 做 app 开发时,为了能适配 Cocoa 架构,AnyObject 就诞生了.它可以代表任意 class 类型(用来替代OC中的 id). 区别 在 Swift 中编译器会对 AnyObject 实例的方法调用做检查,还会返回一个 Optional 的结果. 原理 public ty

  • C语言中回调函数的含义与使用场景详解(2)

    目录 详解C语言中回调函数的含义与使用场景(2) 使用场景一(重定义): 使用场景二(扩展函数功能): 使用场景三(分层): 总结 详解C语言中回调函数的含义与使用场景(2) 引言:在上一篇文章中介绍了回调函数的概念与使用方法,本节将深入地介绍回调函数典型的使用场景.通过使用回调函数可以实现驱动和应用程序的分离解耦,让程序更加地灵活.也可以借助回调函数实现插入自定义代码.分层设计程序的思想. 使用场景一(重定义): 在统一的接口中,动态地改变一个函数的功能.该函数的功能可以是加载参数.或者执行运

  • C语言中回调函数的含义与使用场景详解

    目录 举例 动态改变回调函数的实现的方法: 1)编译时直接赋值 2)运行时实现动态注册 3)作为函数参数传递到指定的函数内 总结 举例 在下述程序中函数 test2_cal() 中调用 函数指针 s_cal 指定的函数执行数值的计算.则 s_cal 指定的那些函数就可以看作一个回调函数. typedef int (*my_calculate_t)(int a, int b); static int cal_sum(int a, int b) { printf("now is sum\r\n&qu

  • swift中defer的实际应用小结

    看看苹果官方的介绍 用 defer 语句在即将离开当前代码块时执行一系列语句.该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的--无论是由于抛出错误而离开,或是由于诸如 return.break 的语句.例如,你可以用 defer 语句来确保文件描述符得以关闭,以及手动分配的内存得以释放. defer 语句将代码的执行延迟到当前的作用域退出之前.该语句由 defer 关键字和要被延迟执行的语句组成.延迟执行的语句不能包含任何控制转移语句,例如 break.return 语句,

  • Go语言defer与return执行的先后顺序详解

    目录 先了解什么是defer defer 的用法 那么defer 和 return有什么联系? 原因: 更进一步理解 省流小结 先了解什么是defer Go语言中的defer与return执行的先后顺序 Go语言的 defer 语句会将其后面跟随的语句进行延迟处理,在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行.也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行.(与栈的先入后出是一个道理,也可以将其理解为入栈和出栈) 举一

  • thinkphp中的多表关联查询的实例详解

    thinkphp中的多表关联查询的实例详解 在进行后端管理系统的编程的时候一般会使用框架来进行页面的快速搭建,我最近使用比较多的就是thinkphp框架,thinkphp框架的应用其实就是把前端和后端进行分割管理,前端用户登录查询系统放在thinkphp中的home文件夹中进行管理,后端管理系统放在thinkphp中的admin文件夹中进行管理.对了,在使用thinkphp框架的时候是是要用到mvc架构的,mvc架构就是model(数据模型).view(视图).controller(控制器)的结

随机推荐