QiuZH‘s Swift Learn|Swift 闭包
闭包简介
闭包是独立的功能块,可以在代码中传递和使用。
Swift
中的闭包类似于C
和Objective-C
中的Block
以及其他编程语言中的lambda
(匿名函数)。闭包可以从定义它们的上下文中捕获和存储对任何常量和变量的引用。这称为关闭这些常量和变量。
Swift
为您处理所有捕获的内存管理。
以上是Swift 官方对闭包的概述。闭包是引用类型,与函数一样。无论何时将函数或闭包赋值给常量或变量,实际上都是将该常量或变量设置为对函数或闭包的引用。
在Functions 中介绍的全局和嵌套函数实际上是闭包的特殊情况。闭包采用以下三种形式之一:
-
全局函数是具有名称且不捕获任何值的闭包。
-
嵌套函数是具有名称并且可以从其封闭函数中捕获值的闭包。
-
闭包表达式是用轻量级语法编写的未命名闭包,可以从其周围的上下文中捕获值。
1. 闭包特点
Swift
的闭包表达式具有干净、清晰的风格,其优化并鼓励在常见场景中使用简洁、整洁的语法。这些优化包括:
- 从上下文推断参数和返回值类型
- 单表达式闭包的隐式返回
- 简写参数名称
- 尾随闭包语法
闭包
1. 闭包表达式语法
闭包表达式语法具有以下一般形式,这跟很多高级编程语言的闭包写法类似:
{ (<#parameters#>) -> <#returnType#> in // 闭包的参数和返回类型
<#statements#> // 闭包包体
}
2. 闭包使用
Swift 的标准库提供了一个名为 的方法sorted(by:)
,我们通过改方法简单了解一下闭包的使用吧。
let arr = [2, 1, 3]
/// 完整的闭包写法。
let result = arr.sorted(by: { (a: Int, b: Int) -> Bool in
return a < b
})
// 如果传入的参数,最后一个参数是闭包,那么可以把闭包放在()外面,叫做尾随闭包。
let _ = arr.sorted() { (a: Int, b: Int) -> Bool in
return a < b
}
// 如果传入的参数只有一个,且是一个闭包,那么这个()可以省略。
let _ = arr.sorted { (a: Int, b: Int) -> Bool in
return a < b
}
// 从单行表达式闭包中隐式返回(闭包体只有一行代码,可以省略return)。
let _ = arr.sorted { (a: Int, b: Int) -> Bool in
a < b
}
// 闭包可更具上下文推断参数和返回值的类型。
let _ = arr.sorted { (a, b) -> Bool in
a < b
}
// 简化闭包的参数名.使用简化参数名,in和之前的描述可以省略。
let _ = arr.sorted {
$0 < $1
}
// 操作符方法
let _ = names.sorted(by: <)
2.1 逃逸闭包
闭包作为函数参数进行传递,但是该闭包并未在函数返回前调用,而是在函数返回后才被调用,则这个闭包被称为逃逸闭包。当我们声明一个以闭包作为参数之一的函数时,我们可以在该闭包参数的类型之前书写@escaping
来表示该闭包允许逃逸。
举个使用场景,当一个函数需要用到异步操作回调的时候需要使用逃逸闭包。 实现闭包逃逸的一种途径是通过将该闭包存储到定义在函数外面的变量中,稍后再去调用。
2.2 自动闭包
autoclosure
是一个被自动创建的闭包,用于包装作为参数传递给函数的表达式。该表达式被自动创建为:不含参数,返回值省略(根据表达式的返回值决定)in
关键字省略,方法体中只含表达式的闭包。
当该函数被调用时,自动闭包会返回表达式的值。我们可以通过在函数类型前使用关键字@autoclosure
把自动闭包外围的花括号{}
都给去掉。但是重点是使用@autoclosure
关键字只限于修饰参数中的闭包,并且该闭包的类型可以有返回值,但绝对不能有参数。
使用关键字@autoclosure
标记闭包参数为自动闭包类型的。调用函数时,传递闭包的实参时,可以像传String
类型的参数一样而不是闭包那样传递。
//定义一个自动闭包的形式
static func autoClosure(operation: @autoclosure ()->String){
print("自动闭包作为参数,输出的删除的数组的字符串:\(operation())")
}
// 自动闭包的使用
escapeClosure.autoClosure(operation: nameArray.remove(at: 0))
注意:过度使用autoclosures
会使我们的代码难以理解。上下文和函数名称应该明确表示该闭包的调用被推迟了。 自动闭包同时也允许逃逸。需要同时使用@autoclosure
和@escaping
属性。
//定义存储自动闭包类型的数组变量
var autoclosureArray : [()->String] = []
//自动闭包实现逃逸
func autoclosureAndEscape(handle: @autoclosure @escaping ()->String) -> Void {
autoclosureArray.append(handle)
}
//执行自动闭包逃逸操作
static func doAutoclosureAndEscape(){
var nameArray = ["我是来自数组的可逃逸的自动闭包"]
//实例化对象
let escapeObj = escapeClosure()
escapeObj.autoclosureAndEscape(handle: "我是可逃逸的自动闭包")
escapeObj.autoclosureAndEscape(handle: nameArray.remove(at: 0))
//逃逸闭包的调用,在函数返回后
for (index,handle) in escapeObj.autoclosureArray.enumerated() {
print("\(handle()):\(index)号")
}
}
//调用
escapeClosure.doAutoclosureAndEscape()//!< 自动闭包逃逸
/**
* 控制台输出:
* 我是可逃逸的自动闭包:0号
* 我是来自数组的可逃逸的自动闭包:1号
*/
2.4 值捕获
闭包可以从定义它的周围上下文中捕获常量和变量。闭包可以在其方法体中引用并修改常量和变量的值,即使定义常量和变量的原始作用域不存在。 在 Swift
中,一个闭包可以捕获值的最简单形式就是嵌套函数。一个嵌套函数可以捕获任何它外围函数的参数也可以捕获定义在外围函数里的常量和变量。
//阐述闭包捕获值的函数方法。
func createIncrementer(forIncrese amount:Int) -> ()->Int {
//定义嵌套函数外部的变量
var total = 0
//定义一个嵌套函数
func increase()->Int {
total += amount
return total
}
//返回此嵌套函数
return increase
}
let increaseByTen = createIncrementer(forIncrese: 10)//increaseByTen其实是内部的嵌套函数(闭包的一种),捕获了`createIncrementer`方法的参数`amount`与方法体中定义的`total`变量。
print(increaseByTen())//!< log:10
print(increaseByTen())//!< log:20
print(increaseByTen())//!< log:30 综上述`increaseByTen`函数捕获了捕获了`createIncrementer`方法的参数`amount`与方法体中定义的`total`变量,并在其内部持有了外部变量`total`和外部方法参数`amount`的副本。以至于`increaseByTen`可以每次调用都能基于`amount`的值,对变量`total`进行递增,并且返回结果。
let increaseBySix = createIncrementer(forIncrese: 6)
print(increaseBySix())//!< log:6
print(increaseBySix())//!< log:12
print(increaseBySix())//!< log:18 综上述:`increaseBySix`是调用了`createIncrementer`生成的一个`()->Int`类型的常量。在生成的过程中,内部的闭包(嵌套函数)`increase`重新捕获了外部变量`total`和外部方法参数`amount`,并返回此方法赋值给了`increaseBySix`,以至于`increaseBySix`和 `increaseByTen` 具备不同的递增系数。本质上这是两个不同的函数类型的常量。