Swift教程之閉包詳解
閉包(Closures)是獨立的函數(shù)代碼塊,能在代碼中傳遞及使用。Swift中的閉包與C和Objective-C中的代碼塊及其它編程語言中的匿名函數(shù)相似。
閉包可以在上下文的范圍內(nèi)捕獲、存儲任何被定義的常量和變量引用。因這些常量和變量的封閉性,而命名為“閉包(Closures)”。Swift能夠?qū)λ心闼懿东@到的引用進(jìn)行內(nèi)存管理。
NOTE
假如你對“捕獲(capturing)”不熟悉,請不要擔(dān)心,具體可以參考Capturing Values(捕獲值)。
全局函數(shù)和嵌套函數(shù)已在 Functions(函數(shù))中介紹過,實際上這些都是特殊的閉包函數(shù)
全局函數(shù)都是閉包,特點是有函數(shù)名但沒有捕獲任何值。
嵌套函數(shù)都是閉包,特點是有函數(shù)名,并且可以在它封閉的函數(shù)中捕獲值。
閉包表達(dá)式都是閉包,特點是沒有函數(shù)名,可以使用輕量的語法在它所圍繞的上下文中捕獲值。
Swift的閉包表達(dá)式有著干凈,清晰的風(fēng)格,并常見情況下對于鼓勵簡短、整潔的語法做出優(yōu)化。這些優(yōu)化包括:
推理參數(shù)及返回值類型源自上下文
隱式返回源于單一表達(dá)式閉包
簡約參數(shù)名
尾隨閉包語法
1、閉包表達(dá)式
嵌套函數(shù)已經(jīng)在Nested Functions(嵌套函數(shù))中有所介紹,是種方便命名和定義自包含代碼塊的一種方式,然而,有時候在編寫簡短函數(shù)式的構(gòu)造器時非常有用,它不需要完整的函數(shù)聲明及函數(shù)名,尤其是在你需要調(diào)用一個或多個參數(shù)的函數(shù)時。
閉包表達(dá)式是一種編寫內(nèi)聯(lián)閉包的方式,它簡潔、緊湊。閉包表達(dá)式提供了數(shù)種語義優(yōu)化,為的是以最簡單的形式編程而不需要大量的聲明或意圖。以下以同一個sort函數(shù)進(jìn)行幾次改進(jìn),每次函數(shù)都更加簡潔,以此說明閉包表達(dá)式的優(yōu)化。
Sort函數(shù)
Swift的標(biāo)準(zhǔn)函數(shù)庫提供了一個名為sort的函數(shù),它通過基于輸出類型排序的閉包函數(shù),給已知類型的數(shù)組數(shù)據(jù)的值排序。一旦完成排序工作,會返回一個同先前數(shù)組相同大小,相同數(shù)據(jù)類型,并且的新數(shù)組,并且這個數(shù)組的元素都在正確排好序的位置上。
The closure expression examples below use the sort function to sort an array of String values in reverse alphabetical order. Here's the initial array to be sorted:
以下的閉包表達(dá)式通過sort函數(shù)將String值按字母順序進(jìn)行排序作說明,這是待排序的初始化數(shù)組。
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
sort函數(shù)需要兩個參數(shù):
一個已知值類型的數(shù)組
一個接收兩個參數(shù)的閉包函數(shù),這兩個參數(shù)的數(shù)據(jù)類型都同于數(shù)組元素。并且
返回一個Bool表明是否第一個參數(shù)應(yīng)排在第二個參數(shù)前或后。
這個例子是一組排序的字符串值,因此需要排序的封閉類型的函數(shù)(字符串,字符串)-> Bool。
構(gòu)造排序閉包的一種方式是書寫一個符合其類型要求的普通函數(shù):backwards,并將其返回值作為 sort 函數(shù)的第二個參數(shù)傳入:
func backwards(s1: String, s2: String) -> Bool {
return s1 > s2
}
var reversed = sort(names, backwards)
// reversed is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
如果backwards函數(shù)參數(shù) s1 大于 s2,則返回true值,表示在新的數(shù)組排序中 s1 應(yīng)該出現(xiàn)在 s2 前。 字符中的 “大于” 表示 “按照字母順序后出現(xiàn)”。 這意味著字母 “B” 大于字母 “A”, 字符串 “Tom” 大于字符串 “Tim”。 其將進(jìn)行字母逆序排序,”Barry” 將會排在 “Alex” 之后,以此類推。
但這是一個相當(dāng)冗長的方式,本質(zhì)上只是做了一個簡單的單表達(dá)式函數(shù) :(a > b)。 下面的例子中,我們利用閉合表達(dá)式可以相比上面的例子更效率的構(gòu)造一個內(nèi)聯(lián)排序閉包。
閉包表達(dá)式語法
閉合表達(dá)式語法具有以下一般構(gòu)造形式:
{ (parameters) -> return type in
statements
}
閉包表達(dá)式語法可以使用常量參數(shù)、變量參數(shù)和 inout 類型作為參數(shù),但皆不可提供默認(rèn)值。 如果你需要使用一個可變的參數(shù),可將可變參數(shù)放在最后,元組類型也可以作為參數(shù)和返回值使用。
下面的例子展示了上面的 backwards 函數(shù)對應(yīng)的閉包表達(dá)式構(gòu)造函數(shù)代碼
reversed = sort(names, { (s1: String, s2: String) -> Bool in
return s1 > s2
})
需要注意的是聲明內(nèi)聯(lián)閉包的參數(shù)和返回值類型與 backwards 函數(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ù)體非常簡約短所以完全可以將上面的backwards函數(shù)縮寫成一行連貫的代碼
reversed = sort(names, { (s1: String, s2: String) -> Bool in return s1 > s2 } )
可以看出 sort 函數(shù)的整體調(diào)用保持不變,還是一對圓括號包含兩個參數(shù)變成了內(nèi)聯(lián)閉包形式、只不過第二個參數(shù)的值變成了。而其中一個參數(shù)現(xiàn)在變成了內(nèi)聯(lián)閉包 (相比于 backwards 版本的代碼)。
根據(jù)上下文推斷類型
因為排序閉包是作為函數(shù)的參數(shù)進(jìn)行傳入的,Swift可以推斷其參數(shù)和返回值的類型。 sort 期望第二個參數(shù)是類型為 (String, String) -> Bool 的函數(shù),因此實際上 String, String 和 Bool 類型并不需要作為閉包表達(dá)式定義中的一部分。 因為所有的類型都可以被正確推斷,返回箭頭 (->) 和 圍繞在參數(shù)周圍的括號也可以被省略:
reversed = sort(names, { s1, s2 in return s1 > s2 } )
實際情況下,通過構(gòu)造內(nèi)聯(lián)閉包表達(dá)式的閉包作為函數(shù)的參數(shù)傳遞給函數(shù)時,都可以判斷出閉包的參數(shù)和返回值類型,這意味著您幾乎不需要利用完整格式構(gòu)造任何內(nèi)聯(lián)閉包。
同樣,如果你希望避免閱讀函數(shù)時可能存在的歧義, 你可以直接明確參數(shù)的類型。
這個排序函數(shù)例子,閉包的目的是很明確的,即排序被替換,而且對讀者來說可以安全的假設(shè)閉包可能會使用字符串值,因為它正協(xié)助一個字符串?dāng)?shù)組進(jìn)行排序。
單行表達(dá)式閉包可以省略 return
單行表達(dá)式閉包可以通過隱藏 return 關(guān)鍵字來隱式返回單行表達(dá)式的結(jié)果,如上版本的例子可以改寫為:
reversed = sort(names, { s1, s2 in s1 > s2 } )
在這個例子中,sort 函數(shù)的第二個參數(shù)函數(shù)類型明確了閉包必須返回一個 Bool 類型值。 因為閉包函數(shù)體只包含了一個單一表達(dá)式 (s1 > s2),該表達(dá)式返回 Bool 類型值,因此這里沒有歧義,return關(guān)鍵字可以省略。
參數(shù)名簡寫
Swift 自動為內(nèi)聯(lián)函數(shù)提供了參數(shù)名稱簡寫功能,可以直接通過 $0,$1,$2等名字來引用閉包的參數(shù)值。
如果在閉包表達(dá)式中使用參數(shù)名稱簡寫,可以在閉包參數(shù)列表中省略對其的定義,并且對應(yīng)參數(shù)名稱簡寫的類型會通過函數(shù)類型進(jìn)行推斷。 in 關(guān)鍵字也同樣可以被省略,因為此時閉包表達(dá)式完全由閉包函數(shù)體構(gòu)成:
reversed = sort(names, { $0 > $1 } )
在這個例子中,$0 和 $1 表示閉包中第一個和第二個 String 類型的參數(shù)。
運算符函數(shù)
運算符函數(shù)實際上是一個更短的方式構(gòu)造以上的表達(dá)式。
reversed = sort(names, >)
更多關(guān)于運算符表達(dá)式的內(nèi)容請查看Operator Functions 。
2、Trailing 閉包
如果您需要將一個很長的閉包表達(dá)式作為最后一個參數(shù)傳遞給函數(shù),可以使用 trailing 閉包來增強(qiáng)函數(shù)的可讀性。
Trailing 閉包是一個書寫在函數(shù)括號之外(之后)的閉包表達(dá)式,函數(shù)支持將其作為最后一個參數(shù)調(diào)用。
func someFunctionThatTakesAClosure(closure: () -> ()) {
// 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
}
注意
如果函數(shù)只需要閉包表達(dá)式一個參數(shù),當(dāng)您使用 trailing 閉包時,您甚至可以把 () 省略掉。
在上例中作為 sort 函數(shù)參數(shù)的字符串排序閉包可以改寫為:
reversed = sort(names) { $0 > $1 }
當(dāng)閉包非常長以至于不能在一行中進(jìn)行書寫時,Trailing 閉包就變得非常有用。 舉例來說,Swift 的 Array 類型有一個 map 方法,其獲取一個閉包表達(dá)式作為其唯一參數(shù)。數(shù)組中的每一個元素調(diào)用一次該閉包函數(shù),并返回該元素所映射的值(也可以是不同類型的值)。 具體的映射方式和返回值類型由閉包來指定。
當(dāng)提供給數(shù)組閉包函數(shù)后,map 方法將返回一個新的數(shù)組,數(shù)組中包含了與原數(shù)組一一對應(yīng)的映射后的值。
下例介紹了如何在 map 方法中使用 trailing 閉包將 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ù)數(shù)字到他們的英文名字之間映射字典。 同時定義了一個準(zhǔn)備轉(zhuǎn)換為字符串的整型數(shù)組。
你現(xiàn)在可以通過傳遞一個 trailing 閉包給 numbers 的 map 方法來創(chuàng)建對應(yīng)的字符串版本數(shù)組。需要注意的時調(diào)用 numbers.map不需要在 map 后面包含任何括號,因為只需要傳遞閉包表達(dá)式這一個參數(shù),并且該閉包表達(dá)式參數(shù)通過 trailing 方式進(jìn)行撰寫:
let strings = numbers.map {
(var number) -> String in
var output = ""
while number > 0 {
output = digitNames[number % 10]! + output
number /= 10
}
return output
}
// strings is inferred to be of type String[]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]
map 在數(shù)組中為每一個元素調(diào)用了閉包表達(dá)式。您不需要指定閉包的輸入?yún)?shù) number 的類型,因為可以通過要映射的數(shù)組類型進(jìn)行推斷。
閉包 number 參數(shù)被聲明為一個變量參數(shù) (變量的具體描述請參看Constant and Variable Parameters),因此可以在閉包函數(shù)體內(nèi)對其進(jìn)行修改。閉包表達(dá)式制定了返回值類型為 String,以表明存儲映射值的新數(shù)組類型為 String。
閉包表達(dá)式在每次被調(diào)用的時候創(chuàng)建了一個字符串并返回。其使用求余運算符 (number % 10) 計算最后一位數(shù)字并利用digitNames 字典獲取所映射的字符串。
注意:
字典 digitNames 下標(biāo)后跟著一個嘆號 (!),因為字典下標(biāo)返回一個可選值 (optional value),表明即使該 key不存在也不會查找失敗。 在上例中,它保證了 number % 10 可以總是作為一個 digitNames 字典的有效下標(biāo) key。 因此嘆號可以用于強(qiáng)展開 (force-unwrap) 存儲在可選下標(biāo)項中的 String 類型值。
從 digitNames 字典中獲取的字符串被添加到輸出的前部,逆序建立了一個字符串版本的數(shù)字。 (在表達(dá)式 number % 10中,如果number為16,則返回6,58返回8,510返回0)。
number 變量之后除以10。 因為其是整數(shù),在計算過程中未除盡部分被忽略。 因此 16變成了1,58變成了5,510變成了51。
整個過程重復(fù)進(jìn)行,直到 number /= 10 為0,這時閉包會將字符串輸出,而map函數(shù)則會將字符串添加到所映射的數(shù)組中。
上例中 trailing 閉包語法在函數(shù)后整潔封裝了具體的閉包功能,而不再需要將整個閉包包裹在 map 函數(shù)的括號內(nèi)。
3、獲取值
閉包可以在其定義的范圍內(nèi)捕捉(引用/得到)常量和變量,閉包可以引用和修改這些值,即使定義的常量和變量已經(jīng)不復(fù)存在了依然可以修改和引用。牛逼吧、
在Swift中最簡單形式是一個嵌套函數(shù),寫在另一個函數(shù)的方法里面。嵌套函數(shù)可以捕獲任何外部函數(shù)的參數(shù),也可以捕獲任何常量和變量在外部函數(shù)的定義。
看下面這個例子,一個函數(shù)方法為makeIncrementor、這是一個嵌套函數(shù),在這個函數(shù)體內(nèi)嵌套了另一個函數(shù)方法:incrementor,在這個incrementor函數(shù)體內(nèi)有兩個參數(shù): runningTotal和amount,實際運作時傳進(jìn)所需的兩個參數(shù)后,incrementor函數(shù)每次被調(diào)用時都會返回一個runningTotal值提供給外部的makeIncrementor使用:
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
而函數(shù)makeincrementor的返回類型值我們可以通過函數(shù)名后面的()-> int得知返回的是一個Int類型的值。如需想學(xué)習(xí)了解更多地函數(shù)返回類型,可以參考: Function Types as Return Types.(超鏈接跳轉(zhuǎn))
我們可以看見makeincrementor這個函數(shù)體內(nèi)首先定義了一個整型變量:runningtotal,初始值為 0 ,而incrementor()函數(shù)最終運行的出來的返回值會賦值給這個整型變量。
makeincrementor函數(shù)()中向外部拋出了一個forIncrement參數(shù)供外部穿參進(jìn)來、一旦有值進(jìn)入函數(shù)體內(nèi)會被函數(shù)實例化替代為amount,而amount會被傳遞進(jìn)內(nèi)嵌的incrementor函數(shù)體中與整型常量runningTotal相加得到一個新的runningTotal并返回。而我們這個主函數(shù)要返回的值是Int類型,runningTotal直接作為最終值被返回出去、makeincrementor函數(shù)()執(zhí)行完畢。
makeincrementor函數(shù)()在其內(nèi)部又定義了一個新的函數(shù)體incrementor,作用就是將外部傳遞過來的值amount 傳進(jìn)incrementor函數(shù)中與整形常量runningTotal相加得到一個新的runningTotal,
單獨的看incrementor函數(shù)、你會發(fā)現(xiàn)這個函數(shù)不尋常:
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
因為incrementor函數(shù)沒有任何的參數(shù),但是在它的函數(shù)方法體內(nèi)卻指向runningTotal和amount,顯而易見、這是incrementor函數(shù)獲取了外部函數(shù)的值amount,incrementor不能去修改它但是卻可以和體內(nèi)的runningTotal相加得出新的runningTotal值返回出去。
不過,由于runningtotal每次被調(diào)用時都會相加改變一次實際值,相應(yīng)地incrementor函數(shù)被調(diào)用時會去加載最新的runningtotal值,而不再是第一次初始化的0.并且需要保證每次runningTotal的值在makeIncrementor函數(shù)體內(nèi)不會丟失直到函數(shù)完全加載完畢。要能確保在函數(shù)體內(nèi)下一次引用時上一次的值依然還在。
注意
Swift中需要明確知道什么時候該引用什么時候該賦值,在incrementor函數(shù)中你不需要注解amount 和runningTotal。Swift還負(fù)責(zé)處理當(dāng)函數(shù)不在需要runningTotal的時候,內(nèi)存應(yīng)該如何去管理。
這里有一個例子makeIncrementor函數(shù):
let incrementByTen = makeIncrementor(forIncrement: 10)
4、引用類型閉包
在上面的例子中,incrementBySeven和incrementByTen是常量,但是這些常量在閉包的狀態(tài)下依然可以被修改。為何?很簡單,因為函數(shù)和閉包是引用類型。
當(dāng)你指定一個函數(shù)或一個閉包常量/變量時、實際上是在設(shè)置該常量或變量是否為一個引用函數(shù)。在上面的例子中,它是閉合的選擇,incrementByTen指的是恒定的,而不是封閉件本身的內(nèi)容。
這也意味著,如果你分配一個封閉兩種不同的常量或變量,這兩個常量或變量將引用同一個閉包:
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// returns a value of 50
相關(guān)文章
Swift中defer關(guān)鍵字推遲執(zhí)行示例詳解
這篇文章主要給大家介紹了關(guān)于Swift中defer關(guān)鍵字推遲執(zhí)行的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03Swift中的可選項Optional解包方式實現(xiàn)原理
這篇文章主要為大家介紹了Swift中的可選項Optional解包方式實現(xiàn)原理示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03swift 錯誤處理do catch try try!使用詳解
這篇文章主要介紹了swift 錯誤處理do catch try try!使用詳解的相關(guān)資料,需要的朋友可以參考下2023-03-03Swift實現(xiàn)監(jiān)聽鍵盤通知及一些處理詳解
這篇文章主要給大家介紹了關(guān)于Swift實現(xiàn)監(jiān)聽鍵盤通知及一些處理的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01Swift 基本數(shù)據(jù)類型詳解總結(jié)
在我們使用任何程序語言編程時,需要使用各種數(shù)據(jù)類型來存儲不同的信息。變量的數(shù)據(jù)類型決定了如何將代表這些值的位存儲到計算機(jī)的內(nèi)存中。在聲明變量時也可指定它的數(shù)據(jù)類型。所有變量都具有數(shù)據(jù)類型,以決定能夠存儲哪種數(shù)據(jù)2021-11-11Swift學(xué)習(xí)教程之SQLite的基礎(chǔ)使用
這篇文章主要給大家介紹了關(guān)于Swift學(xué)習(xí)教程之SQLite的基礎(chǔ)使用,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Swift SQLite具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04SwiftUI 中創(chuàng)建反彈動畫的實現(xiàn)
這篇文章主要介紹了SwiftUI 中創(chuàng)建反彈動畫的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10