閉包可以從定義它的上下文中捕獲常量和變量。
在Swift中,捕獲值最簡單的例子是嵌套函數,舉個例子:
1 func makeIncrementer(forIncrement amount: Int) -> () -> Int { 2 var runningTotal = 0 3 func incrementer() -> Int { 4 runningTotal += amount 5 return runningTotal 6 } 7 return incrementer 8 }
在這個例子中incrementer()捕獲兩個值,分別是amount、runningTotal。可以運行一下,觀察結果:
1 let incrementByTen = makeIncrementer(forIncrement: 10) 2 print(incrementByTen()) //10 3 print(incrementByTen()) //20
4 let incrementByNine = makeIncrementer(forIncrement: 9) 5 print(incrementByNine()) //9 6 print(incrementByNine()) //18
7 print(incrementByTen()) //30
注意:如果你把閉包賦值給一個類實例的一個屬性,並且閉包通過指向(refer fo)實例或者實例的成員捕獲值,那么,在閉包和這個實例間就會有一個強引用環。
閉包是引用類型(Reference Types)
閉包和函數都是引用類型。
Nonescaping Closures
當一個閉包作為參數傳遞給一個函數,但是在函數返回后調用的時候,我們說一個閉包是escaped的。當你聲明一個有一個閉包作為參數的函數的時候,你可以在參數類型前寫@nonescape來暗示這個closure不允許escape。如:
1 func someFunctionWithNonescapingClosure(closure: @noescape () -> Void) { 2 closure() 3 }
把一個閉包標記用@nonescape讓你在閉包內隱式的引用(refer to)self,看下這個例子:
1 class SomeClass { 2 var x = 10 3 func doSomething() { 4 someFunctionWithNonescapingClosure { x = 200 } 5 someFunctionWithEscapingClosure { self.x = 100 } 6 } 7 } 8 9 let instance = SomeClass() 10 instance.doSomething() 11 print(instance.x) 12 // Prints "200" 13 14 completionHandlers.first?() 15 print(instance.x) 16 // Prints "100"
Autoclosures
An autoclosure is a closure that is automatically created to wrap an expression that's being passed as an argument to a function. It doesn't take any arguments, and when it's called, it returns the value of the expression that's wrapped inside of it.
Autoclosures可以延遲計算(delay evaluation),因為直到調用閉包時,閉包內的代碼才被運行。延遲計算對於有副作用或者計算代價昂貴的代碼非常有用,因為你可以控制什么時候代碼進行evaluation。
1 var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] 2 print(customersInLine.count) 3 // Prints "5" 4 5 let customerProvider = { customersInLine.remove(at: 0) } 6 print(customersInLine.count) 7 // Prints "5" 8 9 print("Now serving \(customerProvider())!") 10 // Prints "Now serving Chris!" 11 print(customersInLine.count) 12 // Prints "4"
也可以傳遞給一個參數:
1 // customersInLine is ["Alex", "Ewa", "Barry", "Daniella"] 2 func serve(customer customerProvider: () -> String) { 3 print("Now serving \(customerProvider())!") 4 } 5 serve(customer: { customersInLine.remove(at: 0) } ) 6 // Prints "Now serving Alex!"
使用@autoclosure:
1 // customersInLine is ["Ewa", "Barry", "Daniella"] 2 func serve(customer customerProvider: @autoclosure () -> String) { 3 print("Now serving \(customerProvider())!") 4 } 5 serve(customer: customersInLine.remove(at: 0)) 6 // Prints "Now serving Ewa!"
注意:濫用autoclosure會使代碼晦澀難懂。
@autoclosure屬性隱含了@nonescape屬性,如果你想要一個autoclosure允許esacpe,可以這樣使用 @autoclosure(escaping) ,如:
1 // customersInLine is ["Barry", "Daniella"] 2 var customerProviders: [() -> String] = [] 3 func collectCustomerProviders(_ customerProvider: @autoclosure(escaping) () -> String) { 4 customerProviders.append(customerProvider) 5 } 6 collectCustomerProviders(customersInLine.remove(at: 0)) 7 collectCustomerProviders(customersInLine.remove(at: 0)) 8 9 print("Collected \(customerProviders.count) closures.") 10 // Prints "Collected 2 closures." 11 for customerProvider in customerProviders { 12 print("Now serving \(customerProvider())!") 13 } 14 // Prints "Now serving Barry!" 15 // Prints "Now serving Daniella!"