闭包概念
闭包是可以在你的代码中被传递和引用的功能性独立模块。Swift 中的闭包和 C 以及 Objective-C 中的 blocks 很像,还有其他语言中的匿名函数也类似。
在Swift中,提供三类闭包:
- 全局函数是一个有名字但不会捕获任何值的闭包。
- 嵌套(内嵌)函数是一个有名字并可以捕获到其封闭函数域内的值的闭包。
- 闭包表达式是一个利用轻量级语法所写的,可以捕获其上下文中变量或常量值的匿名闭包。
闭包表达式
闭包表达式语法可以使用常量、变量和inout类型作为参数,不提供默认值。 也可以在参数列表的最后使用可变参数。 元组也可以作为参数和返回值。
闭包表达式语法的一般形式:
1 | { (parameters) -> (return type) in |
- 关键字 in 来区分闭包的头和闭包函数体。
Swift 的标准库提供了一个叫做 sorted(by:) 的方法,会根据你提供的排序闭包将已知类型的数组的值进行排序。使用sorted方法对闭包简化
例子:
1 | let names = ["Chris","Ewa","Barry","Daniella”]` |
类型推断(因排序闭包为实际参数来传递给函数,故 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 | lazy var someClosure: (Int, String) -> String = { |