尾随闭包
如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:例如
- 无参数无返回值
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函数体部分
}
// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
// 闭包主体部分
})
// 以下是使用尾随闭包进行函数调用,一般情况下系统会自动识别尾随闭包
someFunctionThatTakesAClosure {
// 闭包主体部分
}
当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用。举例来说,Swift 的 Array 类型有一个 map(_:) 方法。
- 数组的map(:)函数
这个方法获取一个闭包表达式作为其唯一参数。该闭包函数会为数组中的每一个元素调用一次,并返回该元素所映射的值。具体的映射方式和返回值类型由闭包来指定。
当提供给数组的闭包应用于每个数组元素后,map(:) 方法将返回一个新的数组,数组中包含了与原数组中的元素一一对应的映射后的值。
下例介绍了如何在 map(_:) 方法中使用尾随闭包将 Int 类型数组 [16, 58, 510] 转换为包含对应 String 类型的值的数组["OneSix", "FiveEight", "FiveOneZero"]:
创建两个数组,dagitNames是一个整型数位和它们英文版本名字相映射的字典。numbers是一个数字数组。
let digitNames = [
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]
现在通过传递一个尾随闭包给 numbers 数组的 map(_:) 方法来创建对应的字符串版本数组:
//数字与字符串映射字典
let digitNames = [
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
//数字数组
let numbers = [14, 15 ,35]
//调用数组的map方法
let stringArray = numbers.map { (number) -> String in
var number = number
var outpu = ""
//循环处理数字的每一位
repeat {
//从 digitNames 字典中获取的字符串被添加到 output 的前部,逆序建立了一个字符串版本的数字。(在表达式 number % 10 中,如果 number 为 14,则返回 4,15 返回 5,350 返回 0。)
//number 变量之后除以 10。因为其是整数,在计算过程中未除尽部分被忽略。因此 14 变成了 1,15 变成了 5,350 变成了 35。
outpu = digitNames[number % 10]! + outpu
//整个过程重复进行,直到 number /= 10 为 0,结束循环
number /= 10
} while number > 0
return outpu;
}
print("stringArray=>\(stringArray)")
//stringArray常量被推断为字符串类型数组,即 [String]
//stringArray=>["OneFour", "OneFive", "ThreeFive"]
值捕获
闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
Swift 中,可以捕获值的闭包的最简单形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
举个例子,这有一个叫做 makeIncrementer
的函数,其包含了一个叫做 incrementer
的嵌套函数。嵌套函数 incrementer()
从上下文中捕获了两个值,runningTotal
和 amount
。捕获这些值之后,makeIncrementer
将 incrementer
作为闭包返回。每次调用 incrementer
时,其会以 amount
作为增量增加 runningTotal
的值。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
makeIncrementer
返回类型为 () -> Int
。这意味着其返回的是一个函数,而非一个简单类型的值。该函数在每次调用时不接受参数,只返回一个 Int
类型的值。关于函数返回其他函数的内容,请查看函数类型作为返回类型。
makeIncrementer(forIncrement:)
函数定义了一个初始值为 0
的整型变量 runningTotal
,用来存储当前总计数值。该值为 incrementer
的返回值。
makeIncrementer(forIncrement:)
有一个 Int
类型的参数,其外部参数名为 forIncrement
,内部参数名为 amount
,该参数表示每次 incrementer
被调用时 runningTotal
将要增加的量。makeIncrementer
函数还定义了一个嵌套函数 incrementer
,用来执行实际的增加操作。该函数简单地使 runningTotal
增加 amount
,并将其返回。
如果我们单独考虑嵌套函数 incrementer()
,会发现它有些不同寻常:
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
incrementer()
函数并没有任何参数,但是在函数体内访问了 runningTotal
和 amount
变量。这是因为它从外围函数捕获了 runningTotal
和 amount
变量的引用。捕获引用保证了 runningTotal
和 amount
变量在调用完 makeIncrementer
后不会消失,并且保证了在下一次执行 incrementer
函数时,runningTotal
依旧存在。
注意 为了优化,如果一个值不会被闭包改变,或者在闭包创建后不会改变,Swift 可能会改为捕获并保存一份对值的拷贝。 Swift 也会负责被捕获变量的所有内存管理工作,包括释放不再需要的变量。
下面是一个使用 makeIncrementer
的例子:
let incrementByTen = makeIncrementer(forIncrement: 10)
该例子定义了一个叫做 incrementByTen
的常量,该常量指向一个每次调用会将其 runningTotal
变量增加 10
的 incrementer
函数。调用这个函数多次可以得到以下结果:
incrementByTen()
// 返回的值为10
incrementByTen()
// 返回的值为20
incrementByTen()
// 返回的值为30
如果你创建了另一个 incrementer
,它会有属于自己的引用,指向一个全新、独立的 runningTotal
变量:
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// 返回的值为7
再次调用原来的 incrementByTen
会继续增加它自己的 runningTotal
变量,该变量和 incrementBySeven
中捕获的变量没有任何联系:
incrementByTen()
// 返回的值为40
注意:
如果你将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获了该实例,你将在闭包和该实例间创建一个循环强引用