一个函数和它所捕获的变量\常量环境组合起来,称为闭包
- 一般指定义在函数内部的函数
- 一般它捕获的是外层函数的局部变量\常量
Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)比较相似。
一、闭包表达式
闭包表达式是一种构建内联闭包的方式,它的语法简洁。在保证不丢失它语法清晰明了的同时,闭包表达式提供了几种优化的语法简写形式。
Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:
- 利用上下文推断参数和返回值类型
- 隐式返回单表达式闭包,即单表达式闭包可以省略 return 关键字
- 参数名称缩写
- 尾随闭包语法
1、在Swift中,可以通过func定义一个函数,也可以通过闭包表达式定义一个函数
func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
var fn = {
(v1: Int, v2: Int) -> Int in return v1 + v2
}
fn(10, 20)
闭包表达式的语法形式如下:
{
(参数列表) -> 返回值类型 in 函数体代码
}
2、闭包表达式的简写
以数组的排序方法 sort(by: ) 作为示例
sort(by: )会基于你提供的排序闭包表达式的判断结果对数组中的值(类型确定)进行排序。
//定义一个数组
var arr = [2,4,1,45,23,12,43]
- 以函数形式传入
func temp(v1: Int, v2: Int) -> Bool {
return v1 >= v2
}
arr.sort(by: temp)
- 以闭包的方式
//temp函数对应的闭包表达式形式:
arr.sort { (v1: Int, v2: Int) -> Bool in
return v1 >= v2
}
//根据上下文推断参数类型和返回类型
arr.sort(by: {v1, v2 in return v1 >= v2})
//单表达式闭包的隐式返回 单行表达式闭包可以通过省略 return
arr.sort(by: {v1, v2 in v1 >= v2 })
//参数名称缩写 Swift自动为内联闭包提供了参数名称缩写功能,你可以直接通过 $0,$1,$2... 来顺序调用闭包的参数。
arr.sort(by: {$0 >= $1})
//运算符方法
arr.sort(by: >=)
//尾随闭包 如果将一个闭包表达式作为函数的最后一个参数,使用尾随闭包可以增强可读性
arr.sort(){$0 >= $1}
//如果闭包表达式作为函数的唯一实参,并且使用了尾随闭包,则函数名后面的小括号可以省略
arr.sort{$0 >= $1}
print(arr)
二、逃逸闭包
当闭包作为一个实际参数传递给一个函数的时候,并且它会在函数返回之后调用,我 们就说这个闭包逃逸了。当你声明一个接受闭包作为形式参数的函数时,你可以在形 式参数前写 @escaping 来明确闭包是允许逃逸的。
闭包可以逃逸的一种方法是被储存在定义于函数外的变量里。比如说,很多函数接收 闭包实际参数来作为启动异步任务的回调。函数在启动任务后返回,但是闭包要直到 任务完成——闭包需要逃逸,以便于稍后调用。
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
someFunctionWithEscapingClosure() 函数接收一个闭包作为参数,同时该闭包被添加到一个函数外定义的数组中。此时你需要将这个参数标记为 @escaping ,否则将会编译报错。
让闭包 @escaping 意味着你必须在闭包中显式地引用 self。例如下面代码,传递到someFunctionWithEscapingClosure()的是一个被@escaping标识的逃逸闭包,所以必须要显现的引用self,即必须要写self;而传递到someFunctionWithNonescapingClosure()的是一个非逃逸闭包,所以可以隐式的引用self,即可以不写self。
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x) // 打印出“200”
completionHandlers.first?()
print(instance.x) // 打印出“100”
三、自动闭包
自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行。
func getNum() -> Int{
let a = 10
let b = 11
print("调用")
return a + b
}
// 如果第1个数大于0,返回第一个数。否则返回第2个数
func getFirstPositive(v1: Int, v2: Int) -> Int{
return v1 > 10 ? v1 : v2
}
getFirstPositive(v1: 10, v2: getNum())
例如上述代码,当调用 getFirstPositive() 函数时,不管传入的v1是否大于10,则都会调用getNum()函数,这样就造成了浪费。当时如果将参数v2改成函数类型的参数,则可以让v2延迟加载
func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int{
return v1 > 10 ? v1 : v2()
}
//getFirstPositive(10, {20})
//因闭包作为函数的最后一个参数,可以使用尾随闭包
getFirstPositive(10){20}
通过 @autoclosure 标志标记它的形式参数使用了自动闭包。现在你可以调用函数就像它接收了一个 Int类型的 实际参数而不是闭包。实际参数自动地转换为闭包,因为 customerProvider 形式参数的类型被标记为 @autoclosure 标记。
func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int{
return v1 > 10 ? v1 : v2()
}
//因为使用了@autoclosure进行标记它的形式参数使用了自动闭包,调用的时候不再是//getFirstPositive(10, {20})
getFirstPositive(10, 20)
注意:
- 为了避免与期望冲突,使用了@autoclosure的地方最好明确注释清楚:这个值会被推迟执行
- @autoclosure 会自动将 20 封装成闭包 { 20 }
- @autoclosure 只支持 () -> T 格式的参数 n@autoclosure 并非只支持最后1个参数
- 空合并运算符 ?? 使用了 @autoclosure 技术
- 有@autoclosure、无@autoclosure,构成了函数重载
如果有什么不对的地方,欢迎指正,大家共同进步
网友评论