閉包是自包含的函數(shù)代碼塊,可以在代碼中被傳遞和使用攒盈。Swift 中的閉包與 C 和 Objective-C 中的代碼塊(blocks)以及其他一些編程語言中的匿名函數(shù)比較相似抵拘。
閉包可以捕獲和存儲其所在上下文中任意常量和變量的引用。被稱為包裹常量和變量沦童。 Swift 會為你管理在捕獲過程中涉及到的所有內(nèi)存操作。
在函數(shù)章節(jié)中介紹的全局和嵌套函數(shù)實(shí)際上也是特殊的閉包叹话,閉包采取如下三種形式之一:
- 全局函數(shù)是一個有名字但不會捕獲任何值的閉包
- 嵌套函數(shù)是一個有名字并可以捕獲其封閉函數(shù)域內(nèi)值的閉包
- 閉包表達(dá)式是一個利用輕量級語法所寫的可以捕獲其上下文中變量或常量值的匿名閉包
Swift 的閉包表達(dá)式擁有簡潔的風(fēng)格偷遗,并鼓勵在常見場景中進(jìn)行語法優(yōu)化,主要優(yōu)化如下:
- 利用上下文推斷參數(shù)和返回值類型
- 隱式返回單表達(dá)式閉包驼壶,即單表達(dá)式閉包可以省略 return 關(guān)鍵字
- 參數(shù)名稱縮寫
- 尾隨閉包語法
閉包表達(dá)式####
嵌套函數(shù)是一個在較復(fù)雜函數(shù)中方便進(jìn)行命名和定義自包含代碼模塊的方式氏豌。當(dāng)然,有時候編寫小巧的沒有完整定義和命名的類函數(shù)結(jié)構(gòu)也是很有用處的热凹,尤其是在你處理一些函數(shù)并需要將另外一些函數(shù)作為該函數(shù)的參數(shù)時泵喘。
閉包表達(dá)式是一種利用簡潔語法構(gòu)建內(nèi)聯(lián)閉包的方式。閉包表達(dá)式提供了一些語法優(yōu)化般妙,使得撰寫閉包變得簡單 明了纪铺。下面閉包表達(dá)式的例子通過使用幾次迭代展示了 sorted(by:) 方法定義和語法優(yōu)化的方式。每一次迭代都用更簡潔的方式描述了相同的功能碟渺。
sorted 方法######
Swift 標(biāo)準(zhǔn)庫提供了名為 sorted(by:) 的方法鲜锚,它會根據(jù)你所提供的用于排序的閉包函數(shù)將已知類型數(shù)組中的 值進(jìn)行排序。一旦排序完成苫拍,sorted(by:) 方法會返回一個與原數(shù)組大小相同芜繁,包含同類型元素且元素已正確排序的新數(shù)組。原數(shù)組不會被 sorted(by:) 方法修改绒极。
下面的閉包表達(dá)式示例使用 sorted(by:) 方法對一個 String 類型的數(shù)組進(jìn)行字母逆序排序骏令。以下是初始數(shù)組:
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
sorted(by:) 方法接受一個閉包,該閉包函數(shù)需要傳入與數(shù)組元素類型相同的兩個值垄提,并返回一個布爾類型值來 表明當(dāng)排序結(jié)束后傳入的第一個參數(shù)排在第二個參數(shù)前面還是后面榔袋。如果第一個參數(shù)值出現(xiàn)在第二個參數(shù)值前 面,排序閉包函數(shù)需要返回 true 铡俐,反之返回 false 摘昌。
該例子對一個 String 類型的數(shù)組進(jìn)行排序,因此排序閉包函數(shù)類型需為 (String, String) -> Bool 高蜂。
提供排序閉包函數(shù)的一種方式是撰寫一個符合其類型要求的普通函數(shù)聪黎,并將其作為 sorted(by:) 方法的參數(shù)傳入:
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames 為 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
如果第一個字符串(s1)大于第二個字符串(s2),backward(::) 函數(shù)會返回 true,表示在新的數(shù)組中 s1 應(yīng)該出現(xiàn)在 s2 前稿饰。對于字符串中的字符來說锦秒,“大于”表示“按照字母順序較晚出現(xiàn)”。這意味著字母 "B" 大于字母 "A" 喉镰,字符串 "Tom" 大于字符串 "Tim"旅择。該閉包將進(jìn)行字母逆序排序,"Barry" 將會排在 "Alex" 之前侣姆。
然而生真,以這種方式來編寫一個實(shí)際上很簡單的表達(dá)式 (a > b),確實(shí)太過繁瑣了捺宗。對于這個例子來說柱蟀,利用閉包 表達(dá)式語法可以更好地構(gòu)造一個內(nèi)聯(lián)排序閉包。
閉包表達(dá)式語法######
閉包表達(dá)式語法有如下的一般形式:
{ (parameters) -> returnType in
statements
}
閉包表達(dá)式參數(shù)可以是 in-out 參數(shù)蚜厉,但不能設(shè)定默認(rèn)值长已。也可以使用具名的可變參數(shù)(但是如果可變 參數(shù)不放在參數(shù)列表的最后一位的話,調(diào)用閉包的時時編譯器將報錯)昼牛。元組也可以作為參數(shù)和返回值术瓮。
下面的例子展示了之前 backward(::) 函數(shù)對應(yīng)的閉包表達(dá)式版本的代碼:
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
需要注意的是內(nèi)聯(lián)閉包參數(shù)和返回值類型聲明與 backward(::) 函數(shù)類型聲明相同。在這兩種方式中贰健,都寫成 了 (s1: String, s2: String) -> Bool 胞四。然而在內(nèi)聯(lián)閉包表達(dá)式中,函數(shù)和返回值類型都寫在大括號內(nèi)伶椿,而不是大括號外撬讽。
閉包的函數(shù)體部分由關(guān)鍵字 in 引入。該關(guān)鍵字表示閉包的參數(shù)和返回值類型定義已經(jīng)完成悬垃,閉包函數(shù)體即將開始游昼。
由于這個閉包的函數(shù)體部分如此短,以至于可以將其改寫成一行代碼:
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
該例中 sorted(by:) 方法的整體調(diào)用保持不變尝蠕,一對圓括號仍然包裹住了方法的整個參數(shù)烘豌。然而,參數(shù)現(xiàn)在變成了內(nèi)聯(lián)閉包看彼。
根據(jù)上下文推斷類型######
因?yàn)榕判蜷]包函數(shù)是作為 sorted(by:) 方法的參數(shù)傳入的廊佩,Swift 可以推斷其參數(shù)和返回值的類型。 sorted(by:) 方法被一個字符串?dāng)?shù)組調(diào)用靖榕,因此其參數(shù)必須是 (String, String) -> Bool 類型的函數(shù)标锄。這意味著 (Stri ng, String) 和 Bool 類型并不需要作為閉包表達(dá)式定義的一部分。因?yàn)樗械念愋投伎梢员徽_推斷茁计,返回箭頭( -> )和圍繞在參數(shù)周圍的括號也可以被省略:
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
實(shí)際上料皇,通過內(nèi)聯(lián)閉包表達(dá)式構(gòu)造的閉包作為參數(shù)傳遞給函數(shù)或方法時,總是能夠推斷出閉包的參數(shù)和返回值類型。這意味著閉包作為函數(shù)或者方法的參數(shù)時践剂,你幾乎不需要利用完整格式構(gòu)造內(nèi)聯(lián)閉包鬼譬。
盡管如此,你仍然可以明確寫出有著完整格式的閉包逊脯。如果完整格式的閉包能夠提高代碼的可讀性优质,則我們更鼓 勵采用完整格式的閉包。而在 sorted(by:) 方法這個例子里军洼,顯然閉包的目的就是排序巩螃。由于這個閉包是為了處理字符串?dāng)?shù)組的排序,因此讀者能夠推測出這個閉包是用于字符串處理的匕争。
單表達(dá)式閉包隱式返回######
單行表達(dá)式閉包可以通過省略 return 關(guān)鍵字來隱式返回單行表達(dá)式的結(jié)果避乏,如上版本的例子可以改寫為:
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
在這個例子中,sorted(by:) 方法的參數(shù)類型明確了閉包必須返回一個 Bool 類型值汗捡。因?yàn)殚]包函數(shù)體只包含 了一個單一表達(dá)式 ( s1 > s2 ) 淑际,該表達(dá)式返回 Bool 類型值畏纲,因此這里沒有歧義扇住,return 關(guān)鍵字可以省略。
參數(shù)名稱縮寫######
Swift 自動為內(nèi)聯(lián)閉包提供了參數(shù)名稱縮寫功能盗胀,你可以直接通過 $0 艘蹋, $1 ,$2 來順序調(diào)用閉包的參數(shù)票灰,以此類推女阀。
如果你在閉包表達(dá)式中使用參數(shù)名稱縮寫,你可以在閉包定義中省略參數(shù)列表屑迂,并且對應(yīng)參數(shù)名稱縮寫的類型會 通過函數(shù)類型進(jìn)行推斷浸策。 in 關(guān)鍵字也同樣可以被省略,因?yàn)榇藭r閉包表達(dá)式完全由閉包函數(shù)體構(gòu)成:
reversedNames = names.sorted(by: { $0 > $1 } )
在這個例子中惹盼,$0和$1表示閉包中第一個和第二個 String 類型的參數(shù)庸汗。
運(yùn)算符方法######
實(shí)際上還有一種更簡短的方式來編寫上面例子中的閉包表達(dá)式。Swift 的 String 類型定義了關(guān)于大于號(>)的字符串實(shí)現(xiàn)手报,其作為一個函數(shù)接受兩個 String 類型的參數(shù)并返回 Bool 類型的值蚯舱。而這正好與sorted(by:) 方法的參數(shù)需要的函數(shù)類型相符合。因此掩蛤,你可以簡單地傳遞一個大于號枉昏,Swift 可以自動推斷出 你想使用大于號的字符串函數(shù)實(shí)現(xiàn):
reversedNames = names.sorted(by: >)
尾隨閉包####
如果你需要將一個很長的閉包表達(dá)式作為最后一個參數(shù)傳遞給函數(shù),可以使用尾隨閉包來增強(qiáng)函數(shù)的可讀性揍鸟。尾隨閉包是一個書寫在函數(shù)括號之后的閉包表達(dá)式兄裂,函數(shù)支持將其作為最后一個參數(shù)調(diào)用。在使用尾隨閉包時,你不用寫出它的參數(shù)標(biāo)簽:
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函數(shù)體部分
}
// 以下是不使用尾隨閉包進(jìn)行函數(shù)調(diào)用
someFunctionThatTakesAClosure(closure: {
// 閉包主體部分 })
// 以下是使用尾隨閉包進(jìn)行函數(shù)調(diào)用
someFunctionThatTakesAClosure() {
// 閉包主體部分 }
在閉包表達(dá)式語法一節(jié)中作為 sorted(by:) 方法參數(shù)的字符串排序閉包可以改寫為:
reversedNames = names.sorted() { $0 > $1 }
如果閉包表達(dá)式是函數(shù)或方法的唯一參數(shù)懦窘,則當(dāng)你使用尾隨閉包時前翎,你甚至可以把 () 省略掉:
reversedNames = names.sorted { $0 > $1 }
當(dāng)閉包非常長以至于不能在一行中進(jìn)行書寫時,尾隨閉包變得非常有用畅涂。舉例來說港华,Swift 的 Array 類型有一 個 map(_:) 方法,這個方法獲取一個閉包表達(dá)式作為其唯一參數(shù)午衰。該閉包函數(shù)會為數(shù)組中的每一個元素調(diào)用一 次立宜,并返回該元素所映射的值。具體的映射方式和返回值類型由閉包來指定臊岸。
當(dāng)提供給數(shù)組的閉包應(yīng)用于每個數(shù)組元素后橙数,map(_:) 方法將返回一個新的數(shù)組,數(shù)組中包含了與原數(shù)組中的元素一一對應(yīng)的映射后的值帅戒。
下例介紹了如何在 map(_:) 方法中使用尾隨閉包將 Int 類型數(shù)組 [16, 58, 510] 轉(zhuǎn)換為包含對應(yīng) String 類型的值的數(shù)組 ["OneSix", "FiveEight", "FiveOneZero"] :
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]
如上代碼創(chuàng)建了一個整型數(shù)位和它們英文版本名字相映射的字典灯帮。同時還定義了一個準(zhǔn)備轉(zhuǎn)換為字符串?dāng)?shù)組的整型數(shù)組。
你現(xiàn)在可以通過傳遞一個尾隨閉包給 numbers 數(shù)組的 map(_:) 方法來創(chuàng)建對應(yīng)的字符串版本數(shù)組:
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 常量被推斷為字符串類型數(shù)組逻住,即 [String]
// 其值為 ["OneSix", "FiveEight", "FiveOneZero"]
map(_:) 為數(shù)組中每一個元素調(diào)用了一次閉包表達(dá)式钟哥。你不需要指定閉包的輸入?yún)?shù) number 的類型,因?yàn)榭梢酝ㄟ^要映射的數(shù)組類型進(jìn)行推斷瞎访。
在該例中腻贰,局部變量 number 的值由閉包中的 number 參數(shù)獲得,因此可以在閉包函數(shù)體內(nèi)對其進(jìn)行修改扒秸,(閉包或者函數(shù)的參數(shù)總是常量)播演,閉包表達(dá)式指定了返回類型為 String ,以表明存儲映射值的新數(shù)組類型為 String伴奥。
閉包表達(dá)式在每次被調(diào)用的時候創(chuàng)建了一個叫做 output 的字符串并返回写烤。其使用求余運(yùn)算符( number % 10 )計算最后一位數(shù)字并利用 digitNames 字典獲取所映射的字符串。這個閉包能夠用于創(chuàng)建任意正整數(shù)的字符串表示拾徙。
注意:
字典 digitNames 下標(biāo)后跟著一個嘆號( ! )洲炊,因?yàn)樽值湎聵?biāo)返回一個可選值(optional value),表明該鍵不存在時會查找失敗锣吼。在上例中选浑,由于可以確定 number % 10 總是 digitNames 字典的有效下標(biāo),因此嘆號 可以用于強(qiáng)制解包 (force-unwrap) 存儲在下標(biāo)的可選類型的返回值中的 String 類型的值玄叠。**
從 digitNames 字典中獲取的字符串被添加到 output 的前部古徒,逆序建立了一個字符串版本的數(shù)字。(在表達(dá)式 number % 10 中读恃,如果 number 為 16 隧膘,則返回 6 代态, 58 返回 8 , 510 返回 0 疹吃。)
number 變量之后除以 10 蹦疑。因?yàn)槠涫钦麛?shù),在計算過程中未除盡部分被忽略萨驶。因此 16 變成了 1 歉摧, 58 變成了 5,510 變成了 51腔呜。
整個過程重復(fù)進(jìn)行叁温,直到 number /= 10 為 0 ,這時閉包會將字符串 output 返回核畴,而 map(_:) 方法則會 將字符串添加到映射數(shù)組中膝但。
在上面的例子中,通過尾隨閉包語法谤草,優(yōu)雅地在函數(shù)后封裝了閉包的具體功能跟束,而不再需要將整個閉包包裹在 map(_:) 方法的括號內(nèi)。
值捕獲####
閉包可以在其被定義的上下文中捕獲常量或變量丑孩。即使定義這些常量和變量的原作用域已經(jīng)不存在冀宴,閉包仍然可以在閉包函數(shù)體內(nèi)引用和修改這些值。
Swift 中嚎杨,可以捕獲值的閉包的最簡單形式是嵌套函數(shù)花鹅,也就是定義在其他函數(shù)的函數(shù)體內(nèi)的函數(shù)氧腰。嵌套函數(shù)可以捕獲其外部函數(shù)所有的參數(shù)以及定義的常量和變量枫浙。
舉個例子,這有一個叫做 makeIncrementor 的函數(shù)古拴,其包含了一個叫做 incrementor 的嵌套函數(shù)箩帚。嵌套函數(shù) incrementor() 從上下文中捕獲了兩個值,runningTotal 和 amount黄痪。捕獲這些值之后紧帕,makeIncrementor 將 incrementor 作為閉包返回。每次調(diào)用 incrementor 時桅打,其會以 amount 作為增量增加 runningTotal 的
值是嗜。
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
*makeIncrementor *返回類型為 () -> Int 。這意味著其返回的是一個函數(shù)挺尾,而非一個簡單類型的值鹅搪。該函數(shù)在每次調(diào)用時不接受參數(shù),只返回一個 Int 類型的值遭铺。
makeIncrementor(forIncrement:) 函數(shù)定義了一個初始值為 0 的整型變量 runningTotal 丽柿,用來存儲當(dāng)前總計的返回值恢准。該值為 * incrementor* 的返回值。
makeIncrementor(forIncrement:) 有一個 Int 類型的參數(shù)甫题,其外部參數(shù)名為 forIncrement 馁筐,內(nèi)部參數(shù)名為 amount,該參數(shù)表示每次 * incrementor* 被調(diào)用時 runningTotal 將要增加的量坠非。makeIncrementor 函數(shù)還定義了一個嵌套函數(shù) incrementor敏沉,用來執(zhí)行實(shí)際的增加操作。該函數(shù)簡單地使 runningTotal 增加 amount 炎码,并將其返回赦抖。
如果我們單獨(dú)考慮嵌套函數(shù) incrementor() ,會發(fā)現(xiàn)它有些不同尋常:
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
incrementor() 函數(shù)并沒有任何參數(shù)辅肾,但是在函數(shù)體內(nèi)訪問 runningTotal 和 amount 變量队萤。這是因?yàn)樗鼜?外圍函數(shù)捕獲了 runningTotal 和 amount 變量的引用。捕獲引用保證了 runningTotal 和 amount 變量在調(diào)用完 makeIncrementor 后不會消失矫钓,并且保證了在下一次執(zhí)行 incrementor 函數(shù)時要尔,runningTotal 依舊存在。
注意:為了優(yōu)化新娜,如果一個值不會被閉包改變赵辕,或者在閉包創(chuàng)建后不會改變,Swift 可能會改為捕獲并保存一份 對值的拷貝概龄。 Swift 也會負(fù)責(zé)被捕獲變量的所有內(nèi)存管理工作还惠,包括釋放不再需要的變量。**
下面是一個使用 makeIncrementor 的例子:
let incrementByTen = makeIncrementor(forIncrement: 10)
該例子定義了一個叫做 incrementByTen 的常量,該常量指向一個每次調(diào)用會將其 runningTotal 變量增加 10 的 incrementor 函數(shù)屯阀。調(diào)用這個函數(shù)多次可以得到以下結(jié)果:
incrementByTen()
// 返回的值為10
incrementByTen()
// 返回的值為20
incrementByTen()
// 返回的值為30
如果你創(chuàng)建了另一個 incrementor 毅舆,它會有屬于自己的引用,指向一個全新锣光、獨(dú)立的 runningTotal 變量:
let incrementBySeven = makeIncrementor(forIncrement: 7)
incrementBySeven()
// 返回的值為7
再次調(diào)用原來的 incrementByTen 會繼續(xù)增加它自己的 runningTotal 變量,該變量和 incrementBySeven 中捕獲的變量沒有任何聯(lián)系:
incrementByTen()
// 返回的值為40
注意: 如果你將閉包賦值給一個類實(shí)例的屬性铝耻,并且該閉包通過訪問該實(shí)例或其成員而捕獲了該實(shí)例誊爹,你將在閉包和該實(shí)例間創(chuàng)建一個循環(huán)強(qiáng)引用。Swift 使用捕獲列表來打破這種循環(huán)強(qiáng)引用瓢捉。**
閉包是引用類型####
上面的例子中频丘,incrementBySeven 和 incrementByTen 都是常量,但是這些常量指向的閉包仍然可以增加其捕獲的變量的值泡态。這是因?yàn)楹瘮?shù)和閉包都是引用類型搂漠。
無論你將函數(shù)或閉包賦值給一個常量還是變量,你實(shí)際上都是將常量或變量的值設(shè)置為對應(yīng)函數(shù)或閉包的引 用兽赁。上面的例子中状答,指向閉包的引用 incrementByTen 是一個常量冷守,而并非閉包內(nèi)容本身。
這也意味著如果你將閉包賦值給了兩個不同的常量或變量惊科,兩個值都會指向同一個閉包:
let alsoIncrementByTen = incrementByTen alsoIncrementByTen()
// 返回的值為50
逃逸閉包####
當(dāng)一個閉包作為參數(shù)傳到一個函數(shù)中拍摇,但是這個閉包在函數(shù)返回之后才被執(zhí)行,我們稱該閉包從函數(shù)中逃逸馆截。當(dāng) 你定義接受閉包作為參數(shù)的函數(shù)時充活,你可以在參數(shù)名之前標(biāo)注 @escaping ,用來指明這個閉包是允許“逃逸”出這個函數(shù)的蜡娶。
一種能使閉包“逃逸”出函數(shù)的方法是混卵,將這個閉包保存在一個函數(shù)外部定義的變量中。舉個例子窖张,很多啟動異 步操作的函數(shù)接受一個閉包參數(shù)作為 completion handler幕随。這類函數(shù)會在異步操作開始之后立刻返回,但是閉包 直到異步操作結(jié)束后才會被調(diào)用宿接。在這種情況下赘淮,閉包需要“逃逸”出函數(shù),因?yàn)殚]包需要在函數(shù)返回之后被調(diào) 用睦霎。例如:
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
someFunctionWithEscapingClosure(_:) 函數(shù)接受一個閉包作為參數(shù)梢卸,該閉包被添加到一個函數(shù)外定義的數(shù)組中。如果你不將這個參數(shù)標(biāo)記為 @escaping 副女,就會得到一個編譯錯誤蛤高。
將一個閉包標(biāo)記為 @escaping 意味著你必須在閉包中顯式地引用 self 。比如說碑幅,在下面的代碼中戴陡,傳遞到 someFunctionWithEscapingClosure(_:) 中的閉包是一個逃逸閉包,這意味著它需要顯式地引用 self 枕赵。相對的猜欺,傳遞到 someFunctionWithNonescapingClosure(_:) 中的閉包是一個非逃逸閉包位隶,這意味著它可以隱式引用 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"
自動閉包####
自動閉包是一種自動創(chuàng)建的閉包,用于包裝傳遞給函數(shù)作為參數(shù)的表達(dá)式涧黄。這種閉包不接受任何參數(shù)篮昧,當(dāng)它被調(diào)用的時候,會返回被包裝在其中的表達(dá)式的值笋妥。這種便利語法讓你能夠省略閉包的花括號懊昨,用一個普通的表達(dá)式來代替顯式的閉包。
我們經(jīng)常會調(diào)用采用自動閉包的函數(shù)春宣,但是很少去實(shí)現(xiàn)這樣的函數(shù)酵颁。舉個例子來說嫉你,assert(condition:message:file:line:) 函數(shù)接受自動閉包作為它的 condition 參數(shù)和 message 參數(shù);它的 condition 參數(shù)僅會在 debug 模式下被求值躏惋,它的 message 參數(shù)僅當(dāng) condition 參數(shù)為 false 時被計算求值幽污。
自動閉包讓你能夠延遲求值,因?yàn)橹钡侥阏{(diào)用這個閉包簿姨,代碼段才會被執(zhí)行距误。延遲求值對于那些有副作用(Side Effect)和高計算成本的代碼來說是很有益處的,因?yàn)樗沟媚隳芸刂拼a的執(zhí)行時機(jī)扁位。下面的代碼展示了閉包 如何延時求值准潭。
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())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// 打印出 "4"
盡管在閉包的代碼中,customersInLine 的第一個元素被移除了域仇,不過在閉包被調(diào)用之前刑然,這個元素是不會被移 除的。如果這個閉包永遠(yuǎn)不被調(diào)用暇务,那么在閉包里面的表達(dá)式將永遠(yuǎn)不會執(zhí)行闰集,那意味著列表中的元素永遠(yuǎn)不會 被移除。請注意般卑,customerProvider 的類型不是 String 武鲁,而是 () -> String,一個沒有參數(shù)且返回值為 String 的函數(shù)蝠检。
將閉包作為參數(shù)傳遞給函數(shù)時沐鼠,你能獲得同樣的延時求值行為。
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// 打印出 "Now serving Alex!"
上面的 serve(customer:) 函數(shù)接受一個返回顧客名字的顯式的閉包叹谁。下面這個版本的 serve(customer:) 完成了相同的操作饲梭,不過它并沒有接受一個顯式的閉包,而是通過將參數(shù)標(biāo)記為 @autoclosure 來接收一個自動閉包⊙骈荩現(xiàn)在你可以將該函數(shù)當(dāng)作接受 String 類型參數(shù)(而非閉包)的函數(shù)來調(diào)用憔涉。customerProvider 參數(shù)將自動轉(zhuǎn)化為一個閉包,因?yàn)樵搮?shù)被標(biāo)記了 @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!"
注意:過度使用 autoclosures 會讓你的代碼變得難以理解兜叨。上下文和函數(shù)名應(yīng)該能夠清晰地表明求值是被延遲執(zhí)行的。
如果你想讓一個自動閉包可以“逃逸”衩侥,則應(yīng)該同時使用 @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!"
在上面的代碼中,collectCustomerProviders(_:) 函數(shù)并沒有調(diào)用傳入的 customerProvider 閉包茫死,而是將閉包追加到了 customerProviders 數(shù)組中跪但。這個數(shù)組定義在函數(shù)作用域范圍外,這意味著數(shù)組內(nèi)的閉包能夠在函數(shù)返 回之后被調(diào)用峦萎。因此屡久,customerProvider 參數(shù)必須允許“逃逸”出函數(shù)作用域忆首。