第七章 闭包
3. Capturing Value (捕获值)
闭包可以在它本身被定义的上下文里面捕获到常量和变量的值,然后这个闭包可以在它的闭包体里面进行引用和修改这些值。嵌套函数是一个写在另一个函数体内的函数,这个嵌套函数是一个最简单的捕获值的闭包。一个嵌套函数可以从外部函数的参数里捕获任何值。并且还可以捕获常量和变量。
下面的例子是一个函数makeIncrementer
和一个嵌套函数incrementer
。其中这个嵌套函数会从上下文中捕获两个值分别是runningTotal
和amount
。(After capturing these values, incrementer is returned by makeIncrementer as a closure that increments runningTotal by amount each time it is called.)
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
注意事项:
上面的外函数也就是函数makeIncrementer
,该函数的返回值同样是一个函数(() -> Int
)而不是一个简单的值。返回值的这个函数是一个没有参数并且返回Int
的返回值,这种情况是上个章节中的函数类型用做返回类型(function type as return type
)
这个函数makeIncrementer
定义了一个变量runningTotal
并且给了它一个初始化的值0
。而这个它作为一个嵌套函数的返回值,这个嵌套函数作为外函数的返回值会被首先调用的。就是说每当我们调用函数makeIncrementer
的时候,作为嵌套函数首先会被执行之后会返回runningTotal 作为外函数的参数使用。外函数返回的是这个嵌套函数运算之后的值,所以就有了外函数返回incrementer(return incrementer
).
let increamentByTen = makeIncrementer(forIncrement: 10)
increamentByTen() // 10
increamentByTen() // 20
increamentByTen() //30
赋值操作 定义一个常量increamentByTen
它的值是引用一个被设置过参数的函数,所以let increamentByTen = makeIncrementer(forIncrement: 10)
它的值就是10。调用传参数并进行了计算。当这个参数10传递给外函数的时候。嵌套函数运算就变成了 runningTotal += amount
(runningTotal = runningTotal + 10).
第一次调用increamentByTen()它的值是10,第二次调用它的值是20,第三次调用它的值是30,因为每次调用都会改变makeIncrementer
里面变量runningTotal
的值。
4. Closures are reference types (闭包也是引用类型)
在上面的例子里面我能能看出赋值的时候可以使用闭包作为常量值的引用,不过上个例子顺便把参数传给函数了,还记得什么是闭包吗?从上下文获取(也叫传递)和储存常量和变量的值。在上面的这个例子闭包值是起到逻辑的创建,用来接收makeIncrementer
传递过来的值。把闭包作为一个常量的值,这个值是闭包类型的它用来接收来自上下文传入的参数。
let alsoIncreamentByTen = increamentByTen
alsoIncreamentByTen()
increamentByTen()
let alsoIncreamentByTen = increamentByTen
引用最后一次传入闭包内的参数并进行计算,所以它的值是30,当调用alsoIncreamentByTen()
的时候 他会自动引用let increamentByTen = makeIncrementer(forIncrement: 10)
。也就是10 在大10带入运算多次increamentByTen()
的最终值也就是30。进行运算后得到的就是40。上面例子中的最后一次调用increamentByTen()
是进行了多次复杂的调用引用截止此次调用前的值是40,在进行调用let increamentByTen = makeIncrementer(forIncrement: 10)
得到最终的值是50。
5. Escaping Closure (逃逸闭包)
一个被用做参数的闭包传递给一个函数的时候。这个闭包在函数执行后返回值之后才能被执行。那么我们称这个过程为逃逸闭包,即某个闭包在函数执行返回值之后再被执行的闭包叫做逃逸闭包,一般在函数的参数名之前加@escaping来声明这个闭包是允许从函数中逃逸的。把这个闭包定义在函数外的变量里面,这样才能使这个闭包逃逸出函数
var completionHandlers: [() -> Void] = []
// 函数的参数类型
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
上面的例子里@escaping
表明这个函数返回类型是以 “逃逸” 闭包
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
// 这个时候调用SomeClass只是引用了 SomeClass中的参数。并没有调用逃逸闭包
print(instance.x) // 输出:200
// 逃逸闭包 直接会引用self属性。
completionHandlers.first?()
print(instance.x) // 输出:100
6. Autoclosure (自动闭包)
自动闭包是一个自动创建用于包装传递给函数作为参数的表达式的一个闭包。自动闭包在被调用的时候并不接受任何参数。( an autoclosure is a closure that is automatically created to wrap an expression that’s being passed as an argument to a function )
自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行。延迟求值对于那些有副作用(Side Effect)和高计算成本的代码来说是很有益处的,因为它使得你能控制代码的执行时机。下面的代码展示了闭包 如何延时求值。
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count) // 输出:5
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count) // 输出:5
print("Now serving \(customerProvider())!")
// 输出 "Now serving Chris!"
print(customersInLine.count) // 输出:4
移除了一个customer In line 第0个人的时候 不会改变总数 一般人数是从第一个开始的,
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// Prints "Now serving Alex!
自动闭包也可以允许"逃逸"。可以同时使用@autoclosure 和 @escaping 属性。
// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))
print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"