閉包-Closures
自包含的函數代碼塊
與 C 和 Objective-C 中的代碼塊(blocks))以及其他語言的匿名函數(Lambdas)比較相似
閉包會 捕獲 + 存儲常量和變量的引用(稱為包裹常量和變量)
Swift會管理捕獲過程的內存操作
全局和嵌套函數是特殊的閉包
-
閉包的三種形式:
- 全局函數是一個有名字但不會捕獲任何值的閉包;
- 內嵌函數是一個有名字且能從其上層函數捕獲值的閉包;
- 閉包表達式是一個輕量級語法所寫的可以捕獲其上下文中常量或變量值的沒有名字的閉包。
閉包表達式
- 函數嵌套,代碼塊式的定義和命名形式,十分方便
- 閉包表達式是一種內聯閉包
排序方法
-
sorted(by:)
的方法/函數:接受一個包含排序表達式的閉包參數,返回排好序的新數組 - 原數組不會被
sorted(by:)
方法修改
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
- 排序閉包函數類型需為
(String, String) -> Bool
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
// 將函數當作閉包傳參
var reversedNames = names.sorted(by: backward)
// reversedNames 為 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
閉包表達式語法
- 閉包表達式語法
{ (parameters) -> return type in
statements
}
參數 可以是 in-out 參數
參數不能設定默認值
命名了可變參數,也可以使用此可變參數
元組也可以作為參數和返回值
backward(_:_:)
函數對應的閉包表達式版本的代碼
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
內聯閉包類型與
backward(_:_:)
函數類型聲明相同關鍵字
in
:參數 + 返回值類型 in(分界線) 函數體函數體短的,可簡寫成一行代碼
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
- 參數現在變成了內聯閉包
根據上下文推斷類型
-
省略不寫:參數類型 + 返回值類型
- 字符串數組調用
sorted(by:)
方法 -> 參數是字符串類型 - 排序 -> 返回值是布爾類型
- 字符串數組調用
省略后的寫法
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
- 當把閉包作為行內閉包表達式傳遞給函數或方法時,形式參數類型和返回類型都可以被推斷出來
單表達式閉包的隱式返回
- 省略不寫: return 返回值
- 單表達式 -> 隱式返回
參數名稱縮寫
- 省略不寫:參數名稱
- Swift會自動把閉包的參數名稱命名為:
1 , $2······
-
in
關鍵字也省略不寫
- Swift會自動把閉包的參數名稱命名為:
- 省略后寫法
reversedNames = names.sorted(by: { $0 > $1 } )
運算符方法/函數 - Operator Methods
-
省略不寫:參數
- Swift已經為String類型實現了大于號( >)的運算符方法/函數
@inlinable public static func > (lhs: String, rhs: String) -> Bool
更多關于運算符方法的內容請查看 運算符方法
尾隨閉包
- 尾隨閉包場景:防止閉包太長,無法一行書寫,不再需要將整個閉包包裹在
funName(_:)
方法的括號內 - 函數的最后一個參數為:閉包表達式
- 尾隨閉包,不用寫參數標簽
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函數體部分
}
// 以下是不使用尾隨閉包進行函數調用
someFunctionThatTakesAClosure(closure: {
// 閉包主體部分
})
// 以下是使用尾隨閉包進行函數調用
someFunctionThatTakesAClosure() {
// 閉包主體部分
}
- 函數的參數只有一個閉包表達式時,可把圓括號
()
省略掉
reversedNames = names.sorted { $0 > $1 }
- 例子:
Array
類型的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 = [16, 58, 510]
let strings = numbers.map {
(number) -> String in
var number = number
var output = ""
repeat {
output = digitNames[number % 10]! + output
number /= 10
} while number > 0
return output
}
// strings 常量被推斷為字符串類型數組,即 [String]
// 其值為 ["OneSix", "FiveEight", "FiveOneZero"]
- 根據數組類型,可確定number的類型
digitNames 的下標緊跟著一個感嘆號( ! ),因為字典下標返回一個可選值,表明即使該 key 不存在也不會查找失敗
值捕獲
- 場景:捕獲常量或變量,延長生命周期。
- 定義常量或變量的作用域消失,閉包內部依然可引用和修改這些值
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
值在閉包內不被修改,捕獲一份副本/拷貝即可
Swift會自動處理值捕獲的管理內存
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
// 返回的值為10
incrementByTen()
// 返回的值為20
incrementByTen()
// 返回的值為30
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// 返回的值為7
incrementByTen()
// 返回的值為40
閉包是引用類型
- 函數和閉包都是引用類型
- 將閉包賦值給了兩個不同的常量或變量,兩個值都會指向同一個閉包
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// 返回的值為50
逃逸閉包 - Escaping Closures
- 閉包作為實參傳給函數的時候
- 非逃逸閉包:閉包在函數中(返回前)運行(閉包的引用計數在進入函數和退出函數時保持不變)
- 逃逸閉包:閉包在函數返回/結束后運行(稱該閉包從函數中逃逸,逃逸閉包生命周期長于相關函數函數退出時,逃逸閉包的引用仍然被其他對象持有)
- 閉包參數默認是非逃逸類型。如果需要其逃逸類型的閉包,可以使用關鍵字@escaping
- 場景:
- 異步調用:如果需要調度隊列中異步調用閉包,這個隊列會持有閉包的引用,至于什么時候調用閉包,或者閉包什么時候運行結束都是不可預知的
- 存儲:需要存儲閉包作為屬性,全局變量或者其他類型做稍后使用。
- 閉包逃逸條件:閉包被函數外部的變量引用/保存
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
-
someFunctionWithEscapingClosure(_:)
函數接受一個閉包作為參數,該閉包被添加到一個函數外定義的數組中(不將這個參數標記為@escaping
,就會得到一個編譯錯誤)
-
為了提醒處理循環引用:
- 逃逸閉包要顯式調用self
- 非逃逸閉包可隱式調用self
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// 打印出“200”
completionHandlers.first?()
print(instance.x)
// 打印出“100”
class SomeOtherClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { [self] in x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
- 如果 self 是結構體或者枚舉的實例,可隱式地引用 self
- 總之,當 self 的類型是結構體或者枚舉時,逃逸閉包不能捕獲mutable的 self 引用
- 如同 結構體和枚舉是值類型 中描述的那樣,結構體和枚舉不允許共享可修改性。
struct SomeStruct {
var x = 10
mutating func doSomething() {
someFunctionWithNonescapingClosure { x = 200 } // Ok
someFunctionWithEscapingClosure { x = 100 } // Error
}
}
- someFunctionWithEscapingClosure會報錯,違反了逃逸閉包不能捕獲mutable結構體類型的self
自動閉包
- 使用場景:延時執行, 只傳一個表達式,不傳顯式閉包,省略閉包的花括號
- 下面的代碼展示了閉包如何延遲求值
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]// 客戶名稱-數組
print(customersInLine.count)
// 打印出“5”
let customerProvider = { customersInLine.remove(at: 0) }// 類型不是 `String`,而是 `() -> String`
print(customersInLine.count)
// 打印出“5”
print("Now serving \(customerProvider())!")// 返回被移除的元素
// 打印出“Now serving Chris!”
print(customersInLine.count)
// 打印出“4”
如果閉包不調用,第一個元素永遠不會被移除
customerProvider
的類型不是String
,而是() -> String
,一個沒有參數且返回值為String
的函數把閉包作參數傳給函數,實現延遲處理
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// 打印出“Now serving Alex!”
- 不接受顯式閉包,將函數的閉包形參標記為
@autoclosure
來接收一個自動閉包
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// 打印“Now serving Ewa!”
為了代碼可讀性,避免過度使用autoclosure。
- 自動閉包 + 逃逸閉包。同時使用
@autoclosure
和@escaping
屬性
// customersInLine i= ["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.")
// 打印“Collected 2 closures.”
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}
// 打印“Now serving Barry!”
// 打印“Now serving Daniella!”
- @autoclosure 只支持 () -> T 格式的參數
- @autoclosure 并非只支持最后1個參數
- 空合并運算符 ?? 使用了 @autoclosure 技術
- 有@autoclosure、無@autoclosure,構成了函數重載