//閉包(Closures)
//閉包是獨(dú)立的功能塊,可以在代碼中傳遞和使用挥唠。
//Swift中的閉包類似于 C 和 Objective-C 中的Block以及其他編程語言中的 lambda
//一娃弓、閉包表達(dá)式
//閉包表達(dá)式是一種以簡短文黎、重點(diǎn)突出的語法編寫內(nèi)聯(lián)閉包的方法状共。
//閉包表達(dá)式提供了多種語法優(yōu)化為以縮短的形式編寫閉包蚓哩,而不會(huì)失去清晰度或意圖。
//1.排序方法
//Swift的標(biāo)準(zhǔn)庫提供了一個(gè)名為sorted(by:)的方法件已,該方法根據(jù)您提供閉包的輸出的排序?qū)σ阎愋偷臄?shù)組值進(jìn)行排序笋额。
//一旦完成排序過程,該sorted(by:)方法將返回一個(gè)與舊數(shù)組具有相同類型和大小的新數(shù)組篷扩,其元素按正確的排序順序排列兄猩。原始數(shù)組不會(huì)被該sorted(by:)方法修改。
//下面的閉包表達(dá)式示例使用該sorted(by:)方法按逆字母順序?qū)σ唤MString值進(jìn)行排序鉴未。這是要排序的初始數(shù)組:
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
//該sorted(by:)方法接受一個(gè)閉包枢冤,該閉包采用與數(shù)組內(nèi)容相同類型的兩個(gè)參數(shù),并根據(jù)第一個(gè)值應(yīng)該出現(xiàn)在第二個(gè)值之前還是之后來返回一個(gè)Bool值說明值如何進(jìn)行排序
//如果第一個(gè)值應(yīng)該出現(xiàn)在第二個(gè)值之前铜秆,則排序閉包需要返回true淹真,否則返回false。
//以下這個(gè)例子是對(duì)一個(gè)String值數(shù)組進(jìn)行排序连茧,排序閉包需要是一個(gè)類型為(String, String) -> Bool的函數(shù)
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
//如果第一個(gè)字符串(s1)大于第二個(gè)字符串(s2)核蘸,函數(shù)backward(::)將返回true,表示排序數(shù)組中s1應(yīng)該出現(xiàn)在s2之前
//對(duì)于字符串中的字符啸驯,“大于”表示“在字母表中出現(xiàn)的晚”客扎。這意味著字母"B"“大于”字母"A",并且字符串"Tom"大于字符串"Tim"坯汤。
//這給出了反向字母排序,"Barry"放置在"Alex"之前搀愧,依此類推惰聂。
//2.閉包表達(dá)式語法
//閉包表達(dá)式語法具有以下一般形式:
{ (parameters) -> return type in
statements
}
//該參數(shù)在封閉表達(dá)式語法可以是輸入輸出參數(shù),但是他們不能有一個(gè)默認(rèn)值咱筛。
//如果您命名的是可變參數(shù)搓幌,則可以使用可變參數(shù)。元組也可以用作參數(shù)類型和返回類型迅箩。
//下面的示例顯示了上述backward(::)函數(shù)的閉包表達(dá)式版本:
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
print(reversedNames)
//["Ewa", "Daniella", "Chris", "Barry", "Alex"]
//請注意溉愁,此內(nèi)聯(lián)閉包的參數(shù)和返回類型的聲明與backward(::)函數(shù)的聲明相同。
//在這兩種情況下饲趋,它都寫為(s1: String, s2: String) -> Bool
//但是拐揭,對(duì)于內(nèi)聯(lián)閉包表達(dá)式,參數(shù)和返回類型寫在花括號(hào)內(nèi)奕塑,而不是在花括號(hào)外堂污。
//閉包主體的開始由in關(guān)鍵字開始。這個(gè)關(guān)鍵字表示閉包的參數(shù)和返回類型的定義已經(jīng)完成龄砰,閉包的主體即將開始
//因?yàn)殚]包的主體很短盟猖,它甚至可以寫成一行:
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
print(reversedNames)
//["Ewa", "Daniella", "Chris", "Barry", "Alex"]
//3.從上下文推斷類型
//因?yàn)榕判蜷]包作為參數(shù)傳遞給方法讨衣,Swift可以推斷其參數(shù)的類型和它返回的值的類型。
//該sorted(by:)方法是在字符串?dāng)?shù)組上調(diào)用的式镐,因此其參數(shù)必須是類型為(String, String) -> Bool的函數(shù)反镇。
//這意味著(String, String)和Bool類型不需要寫成閉包表達(dá)式定義的一部分。因?yàn)榭梢酝茢嗨蓄愋湍锕砸部梢允÷苑祷丶^(->)和參數(shù)名稱周圍的括號(hào)
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
print(reversedNames)
//["Ewa", "Daniella", "Chris", "Barry", "Alex"]
//4.單表達(dá)式閉包的隱式返回
//單表達(dá)式閉包可以省略關(guān)鍵字return歹茶,通過從聲明中隱式返回其單個(gè)表達(dá)式的結(jié)果
//如上例的版本可以修改為:
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
print(reversedNames)
//["Ewa", "Daniella", "Chris", "Barry", "Alex"]
//5.簡寫參數(shù)名稱
//Swift自動(dòng)為內(nèi)聯(lián)閉包提供參數(shù)名稱簡寫,可通過參數(shù)1辆亏、$2推斷簡寫參數(shù)名稱的值
//如果您在閉包表達(dá)式中使用這些簡寫參數(shù)名稱,則可以從其定義中省略閉包的參數(shù)列表鳖目。
//簡寫參數(shù)名稱的類型是從預(yù)期的函數(shù)類型推斷出來的扮叨,您使用的編號(hào)最高的簡寫參數(shù)名稱決定了閉包采用的參數(shù)數(shù)量。
//關(guān)鍵字in也可以被省略领迈,因?yàn)殚]包表達(dá)式完全是有閉包創(chuàng)建的
reversedNames = names.sorted(by: { $0 > $1 } )
print(reversedNames)
//["Ewa", "Daniella", "Chris", "Barry", "Alex"]
//在這里彻磁,1引用閉包的第一個(gè)和第二個(gè)字符串參數(shù)。
//因?yàn)?img class="math-inline" src="https://math.jianshu.com/math?formula=1%E6%98%AF%E7%BC%96%E5%8F%B7%E6%9C%80%E9%AB%98%E7%9A%84%E7%AE%80%E5%86%99%E5%8F%82%E6%95%B0%EF%BC%8C%E6%89%80%E4%BB%A5%E9%97%AD%E5%8C%85%E8%A2%AB%E7%90%86%E8%A7%A3%E4%B8%BA%E5%B8%A6%E6%9C%89%E4%B8%A4%E4%B8%AA%E5%8F%82%E6%95%B0%E3%80%82%20%2F%2F%E5%9B%A0%E4%B8%BA%E8%BF%99%E9%87%8C%E7%9A%84%E5%87%BD%E6%95%B0sorted(by%3A)%E9%9C%80%E8%A6%81%E4%B8%80%E4%B8%AA%E9%97%AD%E5%8C%85%EF%BC%8C%E5%AE%83%E7%9A%84%E5%8F%82%E6%95%B0%E9%83%BD%E6%98%AF%E5%AD%97%E7%AC%A6%E4%B8%B2%EF%BC%8C%E7%AE%80%E5%86%99%E5%8F%82%E6%95%B0" alt="1是編號(hào)最高的簡寫參數(shù)狸捅,所以閉包被理解為帶有兩個(gè)參數(shù)衷蜓。 //因?yàn)檫@里的函數(shù)sorted(by:)需要一個(gè)閉包,它的參數(shù)都是字符串尘喝,簡寫參數(shù)" mathimg="1">0和$1都是String類型
//6.運(yùn)算符方法
//實(shí)際上有一種更短的方法來編寫上面的閉包表達(dá)式磁浇。
//Swift的String類型將大于運(yùn)算符(>)的字符串特定實(shí)現(xiàn)定義為一個(gè)方法,該方法具有兩個(gè)String類型參數(shù)朽褪,并返回一個(gè)Bool類型值置吓。
//這與sorted(by:)方法所需的方法類型完全匹配。因此缔赠,您可以簡單地傳入大于運(yùn)算符(>)衍锚,Swift將推斷您要使用其字符串特定的實(shí)現(xiàn):
reversedNames = names.sorted(by: >)
print(reversedNames)
//["Ewa", "Daniella", "Chris", "Barry", "Alex"]
//二、尾隨閉包
//如果您需要將閉包表達(dá)式作為函數(shù)的最終參數(shù)傳遞給函數(shù)嗤堰,并且閉包表達(dá)式很長戴质,那么將其寫為尾隨閉包會(huì)很有用。
//你在函數(shù)調(diào)用的括號(hào)之后寫了一個(gè)尾隨閉包踢匣,即使尾隨閉包仍然是函數(shù)的一個(gè)參數(shù)告匠。
//當(dāng)您使用尾隨閉包語法時(shí),您不會(huì)將第一個(gè)閉包的參數(shù)標(biāo)簽作為函數(shù)調(diào)用的一部分离唬。
//一個(gè)函數(shù)調(diào)用可以包含多個(gè)尾隨閉包凫海;但是,下面的前幾個(gè)示例使用單個(gè)尾隨閉包男娄。
func someFunctionThatTakesAClosure(closure: () -> Void) {
// function body goes here
print("someFunctionThatTakesAClosure")
}
// Here's how you call this function without using a trailing closure:
someFunctionThatTakesAClosure(closure: {
// closure's body goes here
})
// Here's how you call this function with a trailing closure instead:
someFunctionThatTakesAClosure() {
// trailing closure's body goes here
}
//上面閉包表達(dá)式語法部分的字符串排序閉包可以寫在sorted(by:)方法的括號(hào)之外作為尾隨閉包:
reversedNames = names.sorted() { $0 > $1 }
print(reversedNames)
//如果閉包表達(dá)式作為函數(shù)或方法的唯一參數(shù)提供行贪,并且您將該表達(dá)式作為尾隨閉包提供
//當(dāng)在調(diào)用函數(shù)時(shí)漾稀,則無需在函數(shù)或方法名稱后寫一對(duì)括號(hào)():
reversedNames = names.sorted { $0 > $1 }
//當(dāng)閉包足夠長以至于無法將其內(nèi)聯(lián)寫入一行時(shí),尾隨閉包最有用建瘫。
//例如崭捍,Swift的Array類型有一個(gè)map(:)方法,它接受一個(gè)閉包表達(dá)式作為它的單個(gè)參數(shù)啰脚,
//為數(shù)組中的每個(gè)項(xiàng)目調(diào)用一次閉包殷蛇,并為該項(xiàng)目返回一個(gè)替代的映射值(可能是其他類型的)
//您可以通過map(:)方法,在閉包中編寫代碼來指定映射的性質(zhì)和返回值的類型
//以下是使用map(_:)方法將帶有尾隨閉包的Int值數(shù)組轉(zhuǎn)換為String值數(shù)組橄浓。
//該數(shù)組用于創(chuàng)建新數(shù)組:[16, 58, 510] ["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]
//您現(xiàn)在可以使用numbers數(shù)組來創(chuàng)建String值數(shù)組粒梦,方法是將閉包表達(dá)式map(_:)作為尾隨閉包傳遞給數(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
}
print(strings)
// strings is inferred to be of type [String]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]
//該map(_:)方法為數(shù)組中的每個(gè)項(xiàng)目調(diào)用一次閉包表達(dá)式。
//您不需要指定閉包輸入?yún)?shù)number的類型荸实,因?yàn)榭梢詮囊成涞臄?shù)組中的值推斷出類型匀们。
//三、捕捉值
//在Swift 中准给,可以捕獲值的最簡單的閉包形式是嵌套函數(shù)泄朴,它寫在另一個(gè)函數(shù)的主體中。
//嵌套函數(shù)可以捕獲其外部函數(shù)的任何參數(shù)露氮,也可以捕獲外部函數(shù)中定義的任何常量和變量祖灰。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
//下面是一個(gè)調(diào)用makeIncrementer的例子:
let incrementByTen = makeIncrementer(forIncrement: 10)
//這個(gè)例子設(shè)置了一個(gè)被調(diào)用的常量incrementByTen來引用一個(gè)增量函數(shù),每次調(diào)用runningTotal時(shí)它都會(huì)添加10到它的變量中畔规。
//多次調(diào)用該函數(shù)顯示了這種行為:
incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30
//如果您創(chuàng)建第二個(gè)增量器局扶,它將擁有自己存儲(chǔ)的對(duì)新的單獨(dú)runningTotal變量的引用:
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// returns a value of 7
//incrementByTen再次調(diào)用原來的incrementer()會(huì)繼續(xù)增加它自己的runningTotal變量,并且不會(huì)影響被incrementBySeven捕獲的變量:
incrementByTen()
// returns a value of 40
//備注:如果您將閉包分配給類實(shí)例的屬性叁扫,并且該閉包通過引用該實(shí)例或其成員來捕獲該實(shí)例三妈,那么您將在該閉包和該實(shí)例之間創(chuàng)建一個(gè)強(qiáng)引用循環(huán)。
//Swift使用捕獲列表來打破這些強(qiáng)引用循環(huán)
//四陌兑、閉包是引用類型
//在上面的例子中沈跨,incrementBySeven和incrementByTen是常量由捎,但是這些常量所指的閉包仍然能夠捕獲變量runningTotal增加兔综。這是因?yàn)楹瘮?shù)和閉包是引用類型。
//每當(dāng)您將函數(shù)或閉包分配給常量或變量時(shí)狞玛,您實(shí)際上是在將該常量或變量設(shè)置為對(duì)該函數(shù)或閉包的引用软驰。
//這也意味著,如果您將一個(gè)閉包分配給兩個(gè)不同的常量或變量心肪,那么這兩個(gè)常量或變量都指向同一個(gè)閉包锭亏。
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// returns a value of 50
incrementByTen()
// returns a value of 60
//上面的例子表明調(diào)用alsoIncrementByTen與調(diào)用incrementByTen相同。
//因?yàn)樗鼈兌家猛粋€(gè)閉包硬鞍,所以它們都遞增并返回相同的運(yùn)行總數(shù)慧瘤。
//五戴已、轉(zhuǎn)義閉包
//當(dāng)閉包作為參數(shù)傳遞給函數(shù)時(shí),閉包被稱 為對(duì)函數(shù)進(jìn)行轉(zhuǎn)義锅减,在函數(shù)返回后被調(diào)用糖儡。
//當(dāng)您聲明一個(gè)函數(shù)將閉包作為其的參數(shù)之一時(shí),您可以在參數(shù)類型之前寫入”@escaping“以說明允許閉包轉(zhuǎn)義怔匣。
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
//該someFunctionWithEscapingClosure(_:)函數(shù)將一個(gè)閉包作為其參數(shù)并將其添加到在函數(shù)外部聲明的數(shù)組中握联。
//如果你沒有用@escaping標(biāo)記這個(gè)函數(shù)的參數(shù),會(huì)發(fā)生編譯時(shí)錯(cuò)誤每瞒。
//如果self引用了一個(gè)引用self自己的的轉(zhuǎn)義閉包金闽,需要特別考慮一下self。在轉(zhuǎn)義閉包中捕獲self很容易意外地創(chuàng)建一個(gè)強(qiáng)引用循環(huán)
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)
// Prints "200"
completionHandlers.first?()
print(instance.x)
// Prints "100"
//這是通過self包含在閉包的捕獲列表中來捕獲的self的doSomething()版本剿骨,這里self隱式引用
class SomeOtherClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { [self] in x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
//如果self是結(jié)構(gòu)或枚舉代芜,則始終可以隱式引用self
//但是,當(dāng)self是結(jié)構(gòu)或枚舉的情況懦砂,可變引用轉(zhuǎn)義閉包無法捕獲self
struct SomeStruct {
var x = 10
mutating func doSomething() {
someFunctionWithNonescapingClosure { x = 200 } // Ok
// someFunctionWithEscapingClosure { x = 100 } // Error
}
}
//對(duì)上面示例中someFunctionWithEscapingClosure的函數(shù)的調(diào)用會(huì)報(bào)錯(cuò)誤蜒犯,因?yàn)閟elf在一個(gè)可變方法中,所以是可變的荞膘。這違反了在結(jié)構(gòu)中罚随,轉(zhuǎn)義閉包不能捕獲可變引用self的規(guī)則
//六、自動(dòng)閉包
//一個(gè)自動(dòng)閉包是自動(dòng)創(chuàng)建一個(gè)表達(dá)式作為參數(shù)傳遞給函數(shù)的閉包羽资。當(dāng)它被調(diào)用時(shí)淘菩,不用帶任何參數(shù),它返回被包含在里面的的表達(dá)式的值屠升。
//通過編寫一個(gè)普通表達(dá)式而不是顯示的閉包潮改,這種語法方便讓你忽略函數(shù)參數(shù)前后的大括號(hào)
//自動(dòng)閉包調(diào)用函數(shù)非常常見,但是通常不實(shí)現(xiàn)這種函數(shù)
//比如:assert(condition:message:file:line:) 方法給它的條件和消息參數(shù)使用一個(gè)自動(dòng)閉包
//它的條件參數(shù)僅在debug構(gòu)建的時(shí)候被使用腹暖,并且僅在它的條件參數(shù)為false的時(shí)候被使用
//自動(dòng)閉包讓你延遲執(zhí)行代碼汇在,因?yàn)閮?nèi)部代碼直到你調(diào)用自動(dòng)閉包的時(shí)候才會(huì)執(zhí)行
//延遲執(zhí)行對(duì)于有副作用或者計(jì)算量大的代碼非常有用,因?yàn)樽詣?dòng)閉包讓你可以控制什么時(shí)候執(zhí)行代碼
//下面的代碼示例了自動(dòng)閉包如何執(zhí)行
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數(shù)組的第一個(gè)元素在自動(dòng)閉包代碼中被刪除糕殉,數(shù)組元素直到自動(dòng)閉包真實(shí)執(zhí)行才被刪除
//假如閉包一直不調(diào)用,閉包里面的表達(dá)式將永遠(yuǎn)不會(huì)被執(zhí)行殖告,這意味著數(shù)組元素將不會(huì)被刪除
//這里阿蝶,customerProvider的類型不是字符串,而是一個(gè)午餐返回值為String的() -> String函數(shù)
//當(dāng)你傳遞一個(gè)閉包給一個(gè)函數(shù)作為參數(shù)黄绩,延遲執(zhí)行將同樣有效
// 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:)函數(shù)使用顯示閉包返回一個(gè)元素的值。
//在下面的serve(customer:)函數(shù)示例中爽丹,將不使用顯示閉包筑煮,而是通過在函數(shù)參數(shù)類型上加一個(gè)@autoclosure屬性辛蚊,使用自動(dòng)閉包實(shí)現(xiàn)同樣的效果
//現(xiàn)在你可以調(diào)用向函數(shù)傳遞String參數(shù)而不是閉包一樣調(diào)用函數(shù)
//因?yàn)閏ustomerProvider的參數(shù)類型標(biāo)記了@autoclosure屬性,參數(shù)會(huì)自動(dòng)傳遞給閉包
// 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!"
print(customersInLine)
//Prints "["Barry", "Daniella"]"
//備注:過度使用自動(dòng)閉包將是你的代碼難以理解真仲,上下文和函數(shù)名稱應(yīng)該清楚地表明正在延遲執(zhí)行
//如果你希望自動(dòng)閉包允許被轉(zhuǎn)義嚼隘,你可以同時(shí)使用@autoclosure和@escaping屬性
// customersInLine is ["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.")
// Prints "Collected 2 closures."
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"
print(customersInLine)
//Prints "[]"
//在上面的代碼中,collectCustomerProviders(_:)添加閉包到customerProviders數(shù)組袒餐,而不是調(diào)用閉包作為參數(shù)傳遞給customerProvider
//數(shù)組被定義在函數(shù)作用域的外面飞蛹,這意味著數(shù)組中的閉包可以在函數(shù)返回后執(zhí)行
//因此,customerProvider必須允許參數(shù)脫離函數(shù)的作用域