威立山

记录心路历程

0%

Swift闭包

闭包概念

闭包是可以在你的代码中被传递和引用的功能性独立模块。Swift 中的闭包和 C 以及 Objective-C 中的 blocks 很像,还有其他语言中的匿名函数也类似。
在Swift中,提供三类闭包:

  • 全局函数是一个有名字但不会捕获任何值的闭包。
  • 嵌套(内嵌)函数是一个有名字并可以捕获到其封闭函数域内的值的闭包。
  • 闭包表达式是一个利用轻量级语法所写的,可以捕获其上下文中变量或常量值的匿名闭包。

闭包表达式

闭包表达式语法可以使用常量、变量和inout类型作为参数,不提供默认值。 也可以在参数列表的最后使用可变参数。 元组也可以作为参数和返回值。

闭包表达式语法的一般形式:

1
2
3
{ (parameters) -> (return type) in
statements
}
  • 关键字 in 来区分闭包的头和闭包函数体。

Swift 的标准库提供了一个叫做 sorted(by:) 的方法,会根据你提供的排序闭包将已知类型的数组的值进行排序。使用sorted方法对闭包简化
例子:

1
2
3
4
5
6
7
8
9
10
11
let names = ["Chris","Ewa","Barry","Daniella”]`
// 普通函数
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)

// 使用闭包表达式
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})

类型推断(因排序闭包为实际参数来传递给函数,故 Swift 能推断它的形式参数类型和返回类型。)

1
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

从单表达式闭包隐式返回,return 关键字能够被省略
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
简写的实际参数名
reversedNames = names.sorted(by: { $0 > $1 } )
还有一种更加简化的形式,直接使用操作符>,可以写出这样的前提是闭包定义刚好和操作符的定义吻合。
reversedNames = names.sorted(by: >)

逃逸和非逃逸闭包

Swift 的闭包分为 逃逸 与 非逃逸 两种。一个接受逃逸闭包作为参数的函数,逃逸闭包(可能)会在函数返回之后才被调用————也就是说,闭包逃离了函数的作用域。

逃逸闭包通常与异步控制流相关联,如下例所示:

  • 一个函数开启了一个后台任务后立即返回,然后通过一个完成回调(completion handler)报告后台任务的结果。
  • 一个视图类把『按钮点击事件执行的操作』封装成一个闭包,并存储为自身的属性。每次用户点击按钮时,都会调用该闭包。闭包会逃离属性的设置器(setter)。
  • 你使用 DispatchQueue.async 在派发队列(dispatch queue)上安排了一个异步执行的任务。这个闭包任务的生命周期会比 async 的作用域活得更长久。
    与之对应的 DispatchQueue.sync,它会一直等到任务闭包执行完毕后才返回——闭包永远不会逃逸。map 以及标准库中其他的序列和数组的算法也是非逃逸的。

为什么区分闭包的逃逸性和非逃逸性如此重要

简单来说,是为了管理内存。一个闭包会强引用它捕获的所有对象————如果你在闭包中访问了当前对象中的任意属性或实例方法,闭包会持有当前对象,因为这些方法和属性都隐性地携带了一个 self 参数。
这种方式很容易导致循环引用,这解释了为什么编译器会要求你在闭包中显式地写出对 self 的引用。这迫使你考虑潜在的循环引用,并使用捕获列表手动处理。
然而,使用非逃逸的闭包不会产生循环引用————编译器可以保证在函数返回时闭包会释放它捕获的所有对象。因此,编译器只要求在逃逸闭包中明确对 self 的强引用。显然,使用非逃逸闭包是一个更加愉悦的方案。

  • 从 Swift 3.0 开始,非逃逸闭包变成了闭包参数的默认形式。如果你想允许一个闭包参数逃逸,需要给这个类型增加一个 @escaping 的标注。

解决闭包循环引用

解决闭包的循环强引用
Swift 提供了一种优雅的方法来解决这个问题,称之为闭包捕获列表( closuer capture list )
捕获列表中的每一项都由 weak 或 unowned 关键字与类实例的引用(如 self )成对组成

1
2
3
4
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// closure body goes here
}

参考

闭包逃逸和非逃逸