http://wiki.jikexueyuan.com/project/swift/
http://www.swift51.com/swift4.0/
尾隨閉包:閉包作為一個最后一個參數傳給函數
閉包
閉包是可以在你的代碼中被傳遞和引用的功能性獨立模塊。
閉包能夠捕獲和存儲定義在其上下文中的任何常量和變量的引用。
閉包表達式語法
{ (parameters) -> (return type) in
statements
}
let names = ["Chris","Alex","Ewa","Barry","Daniella"]
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
// 函數作為閉包傳入
names.sorted(by: backward)
// 省去函數定義,直接定義閉包
names.sorted(by: { ( s1: String, s2: String) -> Bool in
return s1 > s2
})
// 函數體只有一行,可以寫為一行
names.sorted(by: {( s1: String, s2: String) -> Bool in return s1 > s2 } )
// 類型推斷,可以省略參數類型及返回值
// 當把閉包作為行內閉包表達式傳遞給函數,形式參數類型和返回類型都可以被推斷出來。所以說,當閉包被用作函數的實際參數時你都不需要用完整格式來書寫行內閉包。
names.sorted(by: { s1, s2 in return s1 > s2 } )
// 從單表達式閉包隱式返回,刪掉 return 關鍵字來隱式返回它們單個表達式的結果
names.sorted(by: { s1, s2 in s1 > s2 } )
// 簡寫的實際參數名
// Swift 自動對行內閉包提供簡寫實際參數名,你也可以通過 $0 , $1 , $2 等名字來引用閉包的實際參數值。
// 如果你在閉包表達式中使用這些簡寫實際參數名,那么你可以在閉包的實際參數列表中忽略對其的定義,并且簡寫實際參數名的數字和類型將會從期望的函數類型中推斷出來。 in 關鍵字也能被省略
// in 關鍵字也能被省略
names.sorted(by: { $0 > $1 } )
運算符函數
實際上還有一種更簡短的方式來撰寫上述閉包表達式。Swift 的 String 類型定義了關于大于號( >)的特定字符串實現,讓其作為一個有兩個 String 類型形式參數的函數并返回一個 Bool 類型的值。這正好與 sorted(by:) 方法的第二個形式參數需要的函數相匹配。因此,你能簡單地傳遞一個大于號,并且 Swift 將推斷你想使用大于號特殊字符串函數實現:
reversedNames = names.sorted(by: >)
尾隨閉包
如果你需要將一個很長的閉包表達式作為函數最后一個實際參數傳遞給函數,使用尾隨閉包將增強函數的可讀性。尾隨閉包是一個被書寫在函數形式參數的括號外面(后面)的閉包表達式:
func someFunctionThatTakesAClosure(closure:() -> Void){
//function body goes here
}
//here's how you call this function without using a trailing closure
someFunctionThatTakesAClosure({
//closure's body goes here
})
//here's how you call this function with a trailing closure instead
someFunctionThatTakesAClosure() {
// trailing closure's body goes here
}
如果閉包表達式被用作函數唯一的實際參數并且你把閉包表達式用作尾隨閉包,那么調用這個函數的時候你就不需要在函數的名字后面寫一對圓括號 ( )。
func someFunctionThatTakesAClosure(closure:() -> Void){
//function body goes here
}
someFunctionThatTakesAClosure {
}
func someFunctionThatTakesAClosure(closure:() -> Void, closure2:() -> Void){
//function body goes here
}
someFunctionThatTakesAClosure(closure: {
}) {
}
捕獲值
一個閉包能夠從上下文捕獲已被定義的常量和變量。即使定義這些常量和變量的原作用域已經不存在,閉包仍能夠在其函數體內引用和修改這些值。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
作為一種優化,如果一個值沒有改變或者在閉包的外面,Swift 可能會使用這個值的拷貝而不是捕獲。
Swift也處理了變量的內存管理操作,當變量不再需要時會被釋放。如果你分配了一個閉包給類實例的屬性,并且閉包通過引用該實例或者它的成員來捕獲實例,你將在閉包和實例間建立一個強引用環。
Swift將使用捕獲列表來打破這種強引用環。
閉包是引用類型
無論你什么時候安賦值一個函數或者閉包給常量或者變量,你實際上都是將常量和變量設置為對函數和閉包的引用。這上面這個例子中,閉包選擇 incrementByTen 指向一個常量,而不是閉包它自身的內容。
這也意味著你賦值一個閉包到兩個不同的常量或變量中,這兩個常量或變量都將指向相同的閉包。
逃逸閉包
當閉包作為一個實際參數傳遞給一個函數的時候,我們就說這個閉包逃逸了,因為它可以在函數返回之后被調用。當你聲明一個接受閉包作為形式參數的函數時,你可以在形式參數前寫 @escaping 來明確閉包是允許逃逸的。
閉包可以逃逸的一種方法是被儲存在定義于函數外的變量里。
閉包被添加到數組中,表示可以逃逸,需要添加@excaping 標明。
比如說,很多函數接收閉包實際參數來作為啟動異步任務的回調。函數在啟動任務后返回,但是閉包要直到任務完成——閉包需要逃逸,以便于稍后調用。
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
讓閉包 @escaping 意味著你必須在閉包中顯式地引用 self ,比如說,下面的代碼中,傳給 someFunctionWithEscapingClosure(:) 的閉包是一個逃逸閉包,也就是說它需要顯式地引用 self 。相反,傳給 someFunctionWithNonescapingClosure(:) 的閉包是非逃逸閉包,也就是說它可以隱式地引用 self 。
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
func someFunctionWithNoneescapingClosure(closure: () -> Void) {
closure()
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure {
self.x = 100
}
someFunctionWithNoneescapingClosure {
x = 200
}
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
completionHandlers.first?()
print(instance.x)
自動閉包
把當做參數傳遞給函數的表達式,自動打包為閉包,然后把閉包作為參數來操作。
@autoclosure 將傳入的表達式自動轉為閉包。最終省了一個花括號。
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// Prints "5"
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// Prints "5"
print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// Prints "4"
// 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!"
上邊的函數 serve(customer:) 接收一個明確的返回下一個客戶名稱的閉包。下邊的另一個版本的 serve(customer:) 執行相同的任務但是不使用明確的閉包而是通過 @autoclosure 標志標記它的形式參數使用了自動閉包?,F在你可以調用函數就像它接收了一個 String 實際參數而不是閉包。實際參數自動地轉換為閉包,因為 customerProvider 形式參數的類型被標記為 @autoclosure 標記。
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa!"