重学 Block & Closure 系列之 Closure 篇 | Soledad 

JerryXia 发表于 , 阅读 (0)

介绍

Swift 中的 Closure 和 Objective-C 中的 Block 比较相似。Closure 也可以捕获和保存上下文中任意常量和变量的引用(它自己也是一个引用类型)。另外,Swift 会为你管理所有捕获过程中的内存操作。

Closure 也分三种:

  • 全局函数(对,函数也是闭包的一种),英文叫做 Global functions,一个有名字并且不会捕获任何值的 Closure
  • 嵌套函数,Nested function,它是一个有名字并可以捕获其外部值的闭包。例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    func myFunctionWithNumber(_ someNumber: Int) {
    func increment(_ step: Int) -> Int {
    return someNumber + 10
    }
    let incrementedNumber = increment(someNumber)
    print("The incremented number is \(incrementedNumber)")
    }
    myFunctionWithNumber(5)
    // The incremented number is 15

在这里,func increment(var someNumber: Int) -> Int 就是一个嵌套函数,它捕获了外部的 someNumber 值。

作为优化,如果被 Closure 捕获的值没有被修改,并且在 Closure 创建完之后同样没有被修改的话,那么 Swift 会捕获它的复制

  • 闭包表达式,Closure expression,一个可以捕获其上下文中变常量值的匿名闭包,通常使用轻量级语法编写

我们先来看一下闭包表达式的完整格式:

1
2
3
{ (parameter) -> return type in
statement
}

举例

官方文档中提供了这样一个例子。

1
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

names 可以调用 sorted(by:) 方法,该方法接受一个闭包,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来
表明当排序结束后传入的第一个参数排在第二个参数前面还是后面。

如果将函数作为参数进行传入的话,是这么做:

1
2
3
4
5
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames 为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

如果使用闭包表达式的方法:

1
2
3
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})

关键字 in 将 闭包的参数和返回值类型定义 与 闭包函数体 进行分离操作。

特点

与 Block 相比,Closure 有几个明显的特点。

  • 可以通过上下文推断参数和返回值类型
  • 单表达式 Closure 可以省略 return 关键字
  • 参数名称简写
  • 尾闭包特有的语法

接下来,我们举例说明这几点。

根据上下文推断类型

还是拿上面的 sorted(by:) 举例。由于我们的 names 是字符串数组,那么我们排序时的 s1, s2 可以被推断出来肯定是 String。所以我们不需要再写具体的参数类型以及完整的闭包格式:

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

单表达式 Closure 可以省略 return 关键字

如果 Closure 的 statement 只有一行的话,我们还可以省略 return:

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

参数名称简写

Swift 还给我们提供了参数简写的方法,用来表明相对应的参数(我们甚至能直接省略 in,因为参数同样是可推断的):

1
reversedNames = names.sorted(by: { $0 > $1 })

尾闭包特有的语法

如果你把一个 Closure 作为一个函数的最后一个参数,我们可以把它写成尾闭包的格式。

1
2
3
4
5
6
7
8
9
func someFunctionThatTakesAClosure(closure: () -> Void ) {
print("hello")
closure()
}
someFunctionThatTakesAClosure {
print("my boy")
}
打印出 "hello" "my boy"

对于我们最早的例子,由于 sorted(by:) 同样 Closure 也是最后一个参数,我们可以写成这样:

1
names.sorted() { $0 < $1 }

还有,如果该尾闭包是唯一的参数的话,我们可以省略 ():

1
names.sorted { $0 < $1 }

关于逃逸闭包(Escaping Closures)

如果一个 Closure 作为参数传给一个函数,但是该 Closure 会在函数返回之后再被调用,那么我们称这样闭包是逃逸闭包。这时需要在闭包参数类型前加 @escaping 关键字来允许该闭包可逃逸。否则编译器就会报错。

来举个例子:

1
2
3
4
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler) // 该 Closure 一直未被调用,只有到函数结束后才可以被调用
}

所以说,如果你这样子,那么就可以不加 @escaping :

1
2
3
func someFunctionWithEscapingClosure(completionHandler: () -> Void) {
completionHandler() // 因为你在函数执行过程中就调用了该 Closure,这么说应该明白了吧
}

在这里, completionHandler 直到该函数执行完成后才会被调用。也就是说,这个闭包会逃逸出该函数。因此必须加上 @escape 标记,否则会编译失败。另外,记住在逃逸闭包里是不会隐式加上 self 的,需要你自己明确的写出来。

关于自动闭包(Autoclosures)

苹果引入了自动闭包以及 @autoclosure 关键字,那么它的作用什么呢?简单的说,它把一句表达式自动地封装成一个 Closure。这样有时在语法上看起来就会非常漂亮。但建议谨慎使用,“如果在容易产生歧义或者误解的时候,还是使用完整的闭包写法会比较好。”

比方说我们接受一个闭包,当闭包执行的结果为 true 的时候进行打印:

1
2
3
4
5
func logIfTrue(_ predicate: () -> Bool) {
if predicate() {
print("True")
}
}

调用的时候,我们可以这样写:

1
logIfTrue({return 2 > 1}) //根据闭包简化的特点,我们还可以省略 return。

再深入一点,因为这个闭包是最后一个参数,我们可以使用尾闭包的特点,把大括号拿出来然后省略括号,变成:

1
logIfTrue{ 2 > 1 }

这样还是有点不太自然,如果我们在参数类型前面加上 @autoclosure 关键字:

1
2
3
4
5
func logIfTrue(_ predicate: @autoclosure () -> Bool) {
if predicate() {
print("True")
}
}

那么最终我们可以简写成这样:

1
logIfTrue( 2 > 1 ) // Swift 会**自动**把 2 > 1 这个表达式自动转换为 () -> Bool。

最后要提一句的是,@autoclosure 并不支持带有输入参数的写法,也就是说只有形如 () -> T 的参数才能使用这个特性进行简化。

参考

关注我


本文版权所有,如需转载,请告知原作者并注明出处