我總是覺得 Swift 的語法書有點晦澀??吹牟皇悄敲醋屓死斫狻Uf實在話,語法書的閉包我看了好多遍,說白了我還是沒看懂說了啥。語法書提的例子讓我很是不能理解。哎! 只能說我自己笨吧。
1、閉包的表達式
// 閉包表達式
{ (parameters) -> returnType in
statements
}
閉包的定義:
閉包: 閉包是自包含的函數(shù)代碼塊,可以在代碼中被傳遞和使用。
看著特么蛋疼的定義,不知所云。對于我這種俗人來說都特么是廢話。 說白了我真沒看懂。
對閉包的理解我看的最多的解釋是,閉包和 Objc 的 block 類似。
按照對 block 的理解,block 主要是用來保存一段代碼,在需要的時候進行執(zhí)行。 閉包的功能類似,差不多也是保存一段代碼,在需要的時候進行執(zhí)行。
1.1 閉包表達式分析
// 閉包表達式
{ (parameters) -> returnType in
statements
}
// in 前:閉包的聲明
// in 后:閉包的具體實現(xiàn)
// in 只是作為閉包的聲明和實現(xiàn)的 分隔符
// 使用閉包聲明可以聲明一個閉包的變量
var closures = { (parameters) -> returnType in
}
// 使用 閉包變量我們可以執(zhí)行閉包
closures()
// 閉包的調(diào)用實際上是執(zhí)行閉包中保存的代碼塊 in 后面的具體內(nèi)容
// 我們使用的最簡單的閉包調(diào)用是
{ (parameters) -> returnType in
statements
}()
說明:
-> 表示:執(zhí)行結(jié)果輸出給后面的值
1.2 最簡單的閉包
// 沒有參數(shù)沒有返回值的閉包 (沒有參數(shù),沒有返回值)
{ (()->()) in
}
// 這個和上面是等價的
{ (()->Void) in
}
// 簡單閉包
{() in
}
// 最最最最簡單的閉包 和上面還是等價的
{
}
1.3 閉包的參數(shù)
- 閉包表達式語法可以使用 常量、變量和 inout 類型 作為參數(shù)
- 閉包參數(shù)不能提供默認值。
- 閉包參數(shù)列表中最后一個參數(shù)可以是可變參數(shù)
- 元祖 可以作為 閉包 的參數(shù)和返回值
2. 內(nèi)聯(lián)閉包
內(nèi)聯(lián)閉包:將閉包作為函數(shù)的參數(shù)的閉包叫 內(nèi)聯(lián)閉包
2.1 sort 函數(shù)的使用
// **** sort 方法的使用 ****
// 定義一個數(shù)組
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
// 使用標準的 sort 函數(shù)進行排序操作
// 會根據(jù)您提供的用于排序的閉包函數(shù)將已知類型數(shù)組中的值進行排序.
/*
// 排序用的閉包
{(s1: String, s2: String) -> Bool in
return s1 > s2
}
// 閉包的數(shù)據(jù)類型
(String, String) -> Bool
// sort 函數(shù)確認的參數(shù)類型
(String, String) -> Bool
// 傳入兩個數(shù)組元素相同類型的兩個值,返回 bool 來 表明第一參數(shù)是在第二個參數(shù)的前面還是后面。
// 第一個參數(shù)在第二個參數(shù)前面 閉包返回值為 true
// 第二個參數(shù)在第一個參數(shù)前面 閉包返回值為 false
*/
let newNames = names.sort({ (s1: String, s2: String) -> Bool in
return s1 > s2
})
// 排序后的新數(shù)組
print(newNames)
// sort 函數(shù)并沒有改變原來的數(shù)組
print(names)
// *** 函數(shù)是一個特殊的閉包 ***
// 函數(shù)的數(shù)據(jù)類型為: (String, String) -> Bool
func backwards(s1: String, s2: String) -> Bool {
return s1 > s2
}
// 給 sort 傳入一個函數(shù)( 特殊的閉包 )
var reversed = names.sort(backwards)
print(reversed)
// 將閉包作為參數(shù)傳入函數(shù)這個閉包叫做內(nèi)聯(lián)閉包
// 內(nèi)聯(lián)閉包中包含閉包參數(shù)和閉包的實現(xiàn)(函數(shù)實現(xiàn)),使用 in 關(guān)鍵字進行分隔。
// 閉包簡短可以直接寫作一行
reversed = names.sort({ (s1: String, s2: String) -> Bool in return s1 > s2 } )
print(reversed)

2.2 內(nèi)聯(lián)閉包 —— 類型上下文推斷
排序閉包函數(shù)是作為sort(_:)方法的參數(shù)傳入的,Swift 可以推斷閉包參數(shù)和返回值的類型。
(sort 函數(shù)的參數(shù)類型 == 閉包的類型)
sort(_:)方法被一個字符串數(shù)組調(diào)用,因此其參數(shù)必須是
(String, String) -> Bool類型的函數(shù)。
意味著 (String, String) 和 Bool 類型并不需要作為閉包表達式定義的一部分所有的類型都可以被正確推斷,返回箭頭(->)和圍繞在參數(shù)周圍的括號也可以被省略
reversed = names.sort( { s1, s2 in return s1 > s2 } )
內(nèi)聯(lián)閉包表達式構(gòu)造的閉包 作為參數(shù)傳遞給函數(shù)或方法時,都可以推斷出閉包的參數(shù)和返回值類型。
- 內(nèi)聯(lián)閉包作為函數(shù)或者方法的參數(shù)時,不需要利用完整格式構(gòu)造內(nèi)聯(lián)閉包。(在寫法上可以簡化)
- 完整格式的閉包能夠提高代碼的可讀性,則可以采用完整格式的閉包。
** 簡化寫法慎用,閉包功力不深厚,可能寫完之后自己都要看半天。**
2.3 內(nèi)聯(lián)閉包 —— 單表達式閉包隱式返回
單行表達式閉包可以通過省略return關(guān)鍵字來隱式返回單行表達式的結(jié)果:
reversed = names.sort( { s1, s2 in s1 > s2 } )
// 這個 return 關(guān)鍵字的省略是由 sort 的參數(shù)類型決定的。(其實還是類型推斷的功勞)
// sort 函數(shù)的參數(shù)類型是 (String, String) -> Bool
// 所以你寫不寫 return 閉包都會要求返回一個 bool 值。
2.4 內(nèi)聯(lián)閉包 —— 參數(shù)名稱的縮寫
Swift 自動為內(nèi)聯(lián)閉包提供了參數(shù)名稱縮寫功能,您可以直接通過$0,$1,$2來順序調(diào)用閉包的參數(shù),以此類推。
內(nèi)聯(lián)閉包參數(shù)名稱的縮寫還是基于 類型上下文推斷
sort 函數(shù)的參數(shù)類型是 (String, String) -> Bool
從而可以確定作為 sort 函數(shù)參數(shù)的 內(nèi)聯(lián)閉包 有兩個參數(shù)。
如果按照參數(shù)的列表的順序來確定參數(shù):
* $0 就可以代替 內(nèi)聯(lián)閉包 的第一個參數(shù),
* $1 就可以代替 內(nèi)聯(lián)閉包 的第二個參數(shù),
* $2 就可以代替 內(nèi)聯(lián)閉包 的第三個參數(shù),
* 依次類推 ...
由于內(nèi)聯(lián)閉包的參數(shù)列表中參數(shù)名稱都已經(jīng)確定,所以參數(shù)列表寫不寫出來問題不大。沒有參數(shù)列表和返回值類型(返回值類型直接推斷得出)in 關(guān)鍵字存在的意義也就不大(in 關(guān)鍵字只是作為閉包中聲明和實現(xiàn)的分隔標識),就可以直接省略。
** 簡化版本 **
reversed = names.sort( { $0 > $1 } )
2.5 內(nèi)聯(lián)閉包 —— 運算符函數(shù)
Swift 的 String類型定義了關(guān)于大于號(>)的字符串實現(xiàn),其作為一個函數(shù)接受兩個String類型的參數(shù)并返回Bool類型的值。
** String 的 > 操作符函數(shù)聲明**
(String, String) -> Bool
sort(_:) 函數(shù)的參數(shù)類型
(String, String) -> Bool
使用運算符函數(shù)的簡化
reversed = names.sort(>)
2.6 內(nèi)聯(lián)閉包 —— 尾隨閉包(Trailing Closures)
如果您需要 將一個很長的閉包表達式作為最后一個參數(shù)傳遞給函數(shù),可以使用尾隨閉包來增強函數(shù)的可讀性。
尾隨閉包:是一個書寫在函數(shù)括號之后的閉包表達式。
按照字面理解
只要是函數(shù)的最后一個參數(shù)是閉包,我們就可以使用尾隨閉包。
將內(nèi)聯(lián)閉包寫在括號的外面 —— 我特么可以理解為外聯(lián)閉包嗎?
// 函數(shù)定義
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函數(shù)體部分
}
// 以下是不使用尾隨閉包進行函數(shù)調(diào)用
someFunctionThatTakesAClosure({
// 閉包主體部分
})
// 以下是使用尾隨閉包進行函數(shù)調(diào)用
someFunctionThatTakesAClosure() {
// 閉包主體部分
}
// *** 對個參數(shù),最后一個參數(shù)是閉包 ***
func newSomeFunctionThatTakesAClosure( name: String,closure: () -> Void) {
// 函數(shù)體部分
}
newSomeFunctionThatTakesAClosure("zhangSan1", closure: {
// 閉包主體部分
})
// 由于 XCode 的優(yōu)化,默認生成的是這種格式。
newSomeFunctionThatTakesAClosure("zhangSan2"){
// 閉包主體部分
}
使用尾隨閉包語法對sort 函數(shù)的調(diào)用:
// 尾隨閉包
reversed = names.sort() { $0 > $1 }
// 簡化,省略 ()
reversed = names.sort { $0 > $1 }
當閉包非常長以至于不能在一行中進行書寫時,尾隨閉包變得非常有用。
** 如果函數(shù)需要一個閉包參數(shù)作為參數(shù),且這個參數(shù)是最后一個參數(shù),而這個閉包表達式又很長時 —— 請使用尾隨閉包! **
2.6.1 map 函數(shù)的使用 (尾隨閉包的優(yōu)雅使用)
// *** map 函數(shù)的使用 ****
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]
/**
Swift 的 Array 類型有一個map(_:)方法,
其獲取一個閉包表達式作為其唯一參數(shù)。
該閉包函數(shù)會為數(shù)組中的每一個元素調(diào)用一次,并返回該元素所映射的值。
具體的映射方式和 返回值類型 由閉包來指定。
*/
// 這里使用了尾隨閉包
// map 會為 numbers 中每一個元素調(diào)用一次。調(diào)用 map 的返回值由傳入的閉包決定。
let strings = numbers.map {
(number) -> String in
// 映射的具體實現(xiàn)
var number = number
var output = ""
while number > 0 {
output = digitNames[number % 10]! + output
number /= 10
}
// 返回 numbers 映射 的結(jié)果
return output
}
print(strings)
// strings 常量被推斷為字符串類型數(shù)組,即 [String]
// 其值為 ["OneSix", "FiveEight", "FiveOneZero"]
map 內(nèi)聯(lián)閉包的寫法不能再簡化。map 的參數(shù)不能準確的推斷 閉包的類型 , 只能確定參數(shù)不能確定返回值。
2.7. 捕獲值(Capturing Values)
閉包可以在其被定義的上下文中捕獲常量或變量。
即使定義這些常量和變量的原作用域已經(jīng)不存在,閉包仍然可以在閉包函數(shù)體內(nèi)引用和修改這些值。
(特么的,沒看懂。)
什么叫捕獲,捕獲就是可以在閉包的實現(xiàn) (in 關(guān)鍵字的后面)中可以獲?。玫剑╅]包外面(大括號外面)的 變量和常量。
為什么,即使定義的常量和變量的作用域不存在了,閉包仍然可以在閉包函數(shù)的體內(nèi)引用和修改這些值 ?
結(jié)合對引用計數(shù)器的使用, 每一個傳入到 閉包中的 變量和常量都會進行引用計數(shù)器加 1 操作。只有在閉包所在的作用域結(jié)束后,統(tǒng)一對閉包中的變量和常量的引用計數(shù)器減 1 操作。
對引用計數(shù)器不理解的看objc 的內(nèi)存管理。
閉包的死結(jié) ——> 閉包的循環(huán)引用
** 嵌套函數(shù) (閉包嵌套 )**
Swift 中,可以捕獲值的閉包的最簡單形式是嵌套函數(shù),也就是定義在其他函數(shù)的函數(shù)體內(nèi)的函數(shù)。嵌套函數(shù)可以捕獲其外部函數(shù)所有的參數(shù)以及定義的常量和變量。
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
// 聲明一個變量(寫在參數(shù)列表中的變量(常量)和寫在這里聲明的變量(常量)是等效的)
var runningTotal = 0
// 嵌套函數(shù) ( 特殊的閉包 )
func incrementor() -> Int {
// 在閉包中 會對 amount 和 runningTotal 的引用計數(shù)器 進行 加 1 操作
runningTotal += amount
return runningTotal
}
// 返回一個閉包
/*
在這一步之后,沒有變量對 incrementor 進行引用,閉包就會對 閉包中使用的 常量 和 變量進行計數(shù)器 減 1 操作。
當函數(shù)的作用域完畢后,一切都和諧了
*/
return incrementor
}
// 在之后函數(shù)之后 incrementor 對 函數(shù)的 return 的結(jié)果(閉包)進行了引用。
// 同時 incrementor 也就間接引用了 閉包中的 變量和常量。
let incrementor = makeIncrementor(forIncrement: 100)
// 執(zhí)行 閉包
print(incrementor())
print(incrementor())
print(incrementor())
打印結(jié)果
100
200
300
incrementor 變量的作用域過完之后,閉包中的變量和常量就會釋放。
為了優(yōu)化,如果一個值是不可變的,Swift 可能會改為捕獲并保存一份對值的拷貝。
2.8.1 閉包的循環(huán)引用
如果您將閉包賦值給一個類實例的屬性,并且該閉包通過訪問該實例或其成員而捕獲了該實例,您將創(chuàng)建一個在閉包和該實例間的循環(huán)強引用。
3 閉包是引用類型
說白了就是閉包的值傳遞過程傳遞的是引用。
var incrementor = makeIncrementor(forIncrement: 100)
var oneIncrementor = incrementor
var twoIncrementor = oneIncrementor
print(incrementor())
print(oneIncrementor())
print(twoIncrementor())
打印結(jié)果
100
200
300
// incrementor 、 oneIncrementor 、twoIncrementor 操作的是同一個閉包。
4 非逃逸閉包
逃逸: ** 當一個閉包作為參數(shù)傳到一個函數(shù)中,但是 這個閉包在函數(shù)返回之后才被執(zhí)行**( return 之后),我們稱該閉包從函數(shù)中 逃逸。
定義接受閉包作為參數(shù)的函數(shù)時,你可以在參數(shù)名之前標注@noescape,用來指明這個閉包是不允許“逃逸”出這個函數(shù)的。
** 標注為 @noescape ** 的閉包,只能在函數(shù)體內(nèi)執(zhí)行。生命周期就是函數(shù)體的大括號。
大白話就是 : 不允許在函數(shù)體內(nèi) return 傳入的閉包。不允許將傳入的閉包在函數(shù)體內(nèi)保存到函數(shù)體外的變量中或數(shù)組中。
** 非逃逸閉包 **
// 非逃逸閉包
func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) {
print(#function)
// 閉包只能在函數(shù)體內(nèi)執(zhí)行
closure()
}
someFunctionWithNoescapeClosure {
print("noescape")
}
打印結(jié)果
someFunctionWithNoescapeClosure
noescape
** 逃逸閉包 **
return 逃逸
// 逃逸閉包
func someFunctionWithEscapeClosure(closure: () -> Void) -> (() -> Void){
print(#function)
// 將閉包返回
return closure
}
let escapeClosureFunction = someFunctionWithEscapeClosure {
print("escape")
}
// 在函數(shù)體外執(zhí)行逃逸出來的閉包
escapeClosureFunction()
外部變量引用逃逸
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: () -> Void) {
// 將變量添加到外部的數(shù)組容器中
completionHandlers.append(completionHandler)
}
someFunctionWithEscapingClosure {
print("someFunctionWithEscapingClosure + 1")
}
someFunctionWithEscapingClosure {
print("someFunctionWithEscapingClosure + 2")
}
someFunctionWithEscapingClosure {
print("someFunctionWithEscapingClosure + 3")
}
completionHandlers[0]()
completionHandlers[1]()
completionHandlers[2]()
打印結(jié)果
someFunctionWithEscapingClosure + 1
someFunctionWithEscapingClosure + 2
someFunctionWithEscapingClosure + 3
// 非逃逸閉包逃逸,編譯器會直接報錯
func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) -> (() -> Void) {
print(#function)
return closure
}
// 外部引用逃逸 ,編譯直接報錯
var completionHandlers: [() -> Void] = []
func someFunctionWithNoescapingClosure(@noescape completionHandler: () -> Void) {
completionHandlers.append(completionHandler)
}
閉包標注為@noescape使你能在閉包中隱式地引用self。
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNoescapeClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// prints "200"
completionHandlers.first?()
print(instance.x)
// prints "100"
日常開發(fā)中逃逸閉包的用武之地不多。
語法書中說的使用場景是異步回調(diào)中使用。
5. 自動閉包
自動閉包: 自動創(chuàng)建的閉包,用于包裝傳遞給函數(shù)作為參數(shù)的表達式。
- 自動閉包不接受任何參數(shù)
- 自動閉包調(diào)用的時候,返回 包裝的表達式的值。
- 自動閉包的求值是延遲的(直到調(diào)用閉包的時候才會執(zhí)行閉包代碼段)
** 自動閉包** (有沒有忍無可忍的感覺啊?。?/p>
// 自動閉包沒有參數(shù)列表,自動閉包的返回結(jié)果就是 statements 執(zhí)行的結(jié)果
{
statements
}
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// prints "5"
// 創(chuàng)建了一個 customerProvider 自動閉包
let customerProvider = { customersInLine.removeAtIndex(0) }
print(customersInLine.count)
// prints "5"
// 數(shù)組中的值,只有閉包執(zhí)行后才會移除
print("Now serving \(customerProvider())!")
// prints "Now serving Chris!"
print(customersInLine.count)
// prints "4"
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serveCustomer(customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
// 顯示的傳入一個自動閉包
serveCustomer( { customersInLine.removeAtIndex(0) } )
// prints "Now serving Alex!"
// customersInLine is ["Ewa", "Barry", "Daniella"]
// @autoclosure 關(guān)鍵字標記參數(shù)為一個自動閉包,
func serveCustomer(@autoclosure customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
// 由于函數(shù)的參數(shù)標記是一個自動閉包,可以在函數(shù)的 () 中傳入一個表達式,
// 表達式會自動變?yōu)橐粋€自動閉包
// 第一次看到這個表達式,十個人有九個變傻逼。
serveCustomer(customersInLine.removeAtIndex(0))
// prints "Now serving Ewa!"
過度使用 @autoclosure 會讓你的代碼變得難以理解。上下文和函數(shù)名應(yīng)該能夠清晰地表明求值是被延遲執(zhí)行的。
@autoclosure特性暗含了@noescape特性, 你想讓這個閉包可以“逃逸”,則應(yīng)該使用@autoclosure(escaping)特性.
閉包和函數(shù)的比較
- 全局函數(shù)是一個有名字但不會捕獲任何值的閉包
- 嵌套函數(shù)是一個有名字并可以捕獲其封閉函數(shù)域內(nèi)值的閉包
- 閉包表達式是一個利用輕量級語法所寫的可以捕獲其上下文中變量或常量值的匿名閉包
推薦使用場景
- 利用上下文推斷參數(shù)和返回值類型
- 隱式返回單表達式閉包,即單表達式閉包可以省略return關(guān)鍵字
- 參數(shù)名稱縮寫
- 尾隨(Trailing)閉包語法