国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

Swift閉包詳解

原創(chuàng) 2017-01-11 13:58:50 633
摘要:閉包是功能性自包含模塊,可以在代碼中被傳遞和使用。 Swift 中的閉包與 C 和 Objective-C中的 blocks 以及其他一些編程語言中的 lambdas 比較相似。閉包可以 捕獲 和存儲其所在上下文中任意常量和變量的引用。 這就是所謂的閉合并包裹著這些常量和變量,俗稱閉包。S

閉包是功能性自包含模塊,可以在代碼中被傳遞和使用。 Swift 中的閉包與 C 和 Objective-C中的 blocks 以及其他一些編程語言中的 lambdas 比較相似。

閉包可以 捕獲 和存儲其所在上下文中任意常量和變量的引用。 這就是所謂的閉合并包裹著這些常量和變量,俗稱閉包。Swift會為您管理在 捕獲 過程中涉及到的內(nèi)存操作。
注意:如果您不熟悉 捕獲 (capturing) 這個概念也不用擔(dān)心,后面會詳細(xì)對其進(jìn)行介紹。在Swift函數(shù)章節(jié)中介紹的全局和嵌套函數(shù)實際上也是特殊的閉包,閉包采取如下三種形式之一:

全局函數(shù)是一個有名字但不會捕獲任何值的閉包

嵌套函數(shù)是一個有名字并可以捕獲其封閉函數(shù)域內(nèi)值的閉包

閉包表達(dá)式是一個利用輕量級語法所寫的可以捕獲其上下文中變量或常量值的沒有名字的閉包


Swift的閉包表達(dá)式擁有簡潔的風(fēng)格,并鼓勵在常見場景中以實現(xiàn)語法優(yōu)化,主要優(yōu)化如下:

利用上下文推斷參數(shù)和返回值類型

單表達(dá)式(single-expression)閉包可以省略 return 關(guān)鍵字

參數(shù)名稱簡寫

Trailing 閉包語法

閉包表達(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á)式的例子通過使用幾次迭代展示了 sort 函數(shù)定義和語法優(yōu)化的方式。 每一次迭代都用更簡潔的方式描述了相同的功能。

sort 函數(shù)

Swift 標(biāo)準(zhǔn)庫提供了 sort 函數(shù),會根據(jù)您提供的排序閉包將已知類型數(shù)組中的值進(jìn)行排序。 一旦排序完成,函數(shù)會返回一個與原數(shù)組大小相同的新數(shù)組,該數(shù)組中包含已經(jīng)正確排序的同類型元素。

下面的閉包表達(dá)式示例使用 sort 函數(shù)對一個 String 類型的數(shù)組進(jìn)行字母逆序排序,以下是初始數(shù)組值:
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
排序函數(shù)有兩個參數(shù):

已知類型值的數(shù)組。

一個閉包,采用相同類型的數(shù)組的內(nèi)容的兩個參數(shù),并返回一個布爾值來表示是否將第一個值在排序時放到第二個值的前面或是后面。如果第一個值應(yīng)該出現(xiàn)第二個值之前,閉包需要返回true,否則返回false。


該例子對一個 String 類型的數(shù)組進(jìn)行排序,因此排序閉包需為 (String, String) -> Bool 類型的函數(shù)。

提供排序閉包的一種方式是撰寫一個符合其類型要求的普通函數(shù),并將其作為 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"]
如果第一個字符串 (s1) 大于第二個字符串 (s2),backwards 函數(shù)則返回 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á)式語法有如下一般形式:
{ (parameters) -> returnType instatements}
閉包表達(dá)式語法可以使用常量、變量和 inout 類型作為參數(shù),但不提供默認(rèn)值。 也可以在參數(shù)列表的最后使用可變參數(shù)。元組也可以作為參數(shù)和返回值。

下面的例子展示了之前 backwards 函數(shù)對應(yīng)的閉包表達(dá)式版本的代碼:
reversed = sort(names, { (s1: String, s2: String) -> Bool inreturn 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ù)體部分如此短以至于可以將其改寫成一行代碼:
reversed = sort(names, { (s1: String, s2: String) -> Bool in return s1 > s2 } )
這說明 sort 函數(shù)的整體調(diào)用保持不變,一對圓括號仍然包裹住了函數(shù)中整個參數(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 } )
實際上任何情況下,通過內(nèi)聯(lián)閉包表達(dá)式構(gòu)造的閉包作為參數(shù)傳遞給函數(shù)時,都可以推斷出閉包的參數(shù)和返回值類型,這意味著您幾乎不需要利用完整格式構(gòu)造任何內(nèi)聯(lián)閉包。

然而,你也可以使用明確的類型,如果你想它避免讀者閱讀可能存在的歧義,這樣還是值得鼓勵的。這個排序函數(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ù)

實際上還有一種更簡短的方式來撰寫上面例子中的閉包表達(dá)式。 Swift的 String 類型定義了關(guān)于大于號 (>) 的字符串實現(xiàn),讓其作為一個函數(shù)接受兩個 String 類型的參數(shù)并返回 Bool 類型的值。 而這正好與 sort 函數(shù)的第二個參數(shù)需要的函數(shù)類型相符合。 因此,您可以簡單地傳遞一個大于號,Swift可以自動推斷出您想使用大于號的字符串函數(shù)實現(xiàn):
reversed = sort(names, >)
更多關(guān)于運算符表達(dá)式的內(nèi)容請查看 Operator Functions 。

Trailing 閉包

如果您需要將一個很長的閉包表達(dá)式作為最后一個參數(shù)傳遞給函數(shù),可以使用 trailing 閉包來增強函數(shù)的可讀性。

Trailing 閉包是一個書寫在函數(shù)括號之外(之后)的閉包表達(dá)式,函數(shù)支持將其作為最后一個參數(shù)調(diào)用。
func someFunctionThatTakesAClosure(closure: () -> ()) {// 函數(shù)體部分}// 以下是不使用 trailing 閉包進(jìn)行函數(shù)調(diào)用someFunctionThatTakesAClosure({// 閉包主體部分})// 以下是使用 trailing 閉包進(jìn)行函數(shù)調(diào)用someFunctionThatTakesAClosure() {// 閉包主體部分}
注意:如果函數(shù)只需要閉包表達(dá)式一個參數(shù),當(dāng)您使用 trailing 閉包時,您甚至可以把 () 省略掉。 NOTE在上例中作為 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 invar output = ""while number > 0 {output = digitNames[number % 10]! + outputnumber /= 10}return output}// strings 常量被推斷為字符串類型數(shù)組,即 String[]// 其值為 ["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。 因此嘆號可以用于強展開 (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)。

捕獲 (Caputure)

閉包可以在其定義的上下文中捕獲常量或變量。 即使定義這些常量和變量的原作用域已經(jīng)不存在,閉包仍然可以在閉包函數(shù)體內(nèi)引用和修改這些值。

Swift最簡單的閉包形式是嵌套函數(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 = 0func incrementor() -> Int {runningTotal += amountreturn runningTotal}return incrementor}
makeIncrementor 返回類型為 () -> Int。 這意味著其返回的是一個函數(shù),而不是一個簡單類型值。 該函數(shù)在每次調(diào)用時不接受參數(shù)只返回一個 Int 類型的值。 關(guān)于函數(shù)返回其他函數(shù)的內(nèi)容,請查看Function Types as Return Types。

makeIncrementor 函數(shù)定義了一個整型變量 runningTotal (初始為0) 用來存儲當(dāng)前增加總數(shù)。 該值通過 incrementor 返回。

makeIncrementor 有一個 Int 類型的參數(shù),其外部命名為 forIncrement, 內(nèi)部命名為 amount,表示每次 incrementor 被調(diào)用時runningTotal 將要增加的量。

incrementor 函數(shù)用來執(zhí)行實際的增加操作。 該函數(shù)簡單地使 runningTotal 增加 amount,并將其返回。

如果我們單獨看這個函數(shù),會發(fā)現(xiàn)看上去不同尋常:
func incrementor() -> Int {runningTotal += amountreturn runningTotal}
incrementor 函數(shù)并沒有獲取任何參數(shù),但是在函數(shù)體內(nèi)訪問了 runningTotal 和 amount 變量。這是因為其通過捕獲在包含它的函數(shù)體內(nèi)已經(jīng)存在的 runningTotal 和 amount 變量而實現(xiàn)。

由于沒有修改 amount 變量,incrementor 實際上捕獲并存儲了該變量的一個副本,而該副本隨著 incrementor 一同被存儲。

然而,因為每次調(diào)用該函數(shù)的時候都會修改 runningTotal 的值,incrementor 捕獲了當(dāng)前 runningTotal 變量的引用,而不是僅僅復(fù)制該變量的初始值。捕獲一個引用保證了當(dāng) makeIncrementor 結(jié)束時候并不會消失,也保證了當(dāng)下一次執(zhí)行 incrementor 函數(shù)時,runningTotal 可以繼續(xù)增加。
注意:Swift 會決定捕獲引用還是拷貝值。 您不需要標(biāo)注 amount 或者 runningTotal 來聲明在嵌入的 incrementor 函數(shù)中的使用方式。 Swift 同時也處理 runingTotal 變量的內(nèi)存管理操作,如果不再被 incrementor 函數(shù)使用,則會被清除。下面為一個使用 makeIncrementor 的例子:
let incrementByTen = makeIncrementor(forIncrement: 10)
該例子定義了一個叫做 incrementByTen 的常量,該常量指向一個每次調(diào)用會加10的 incrementor 函數(shù)。 調(diào)用這個函數(shù)多次可以得到以下結(jié)果:
incrementByTen()// 返回的值為10incrementByTen()// 返回的值為20incrementByTen()// 返回的值為30
如果您創(chuàng)建了另一個 incrementor,其會有一個屬于自己的獨立的 runningTotal 變量的引用。 下面的例子中,incrementBySevne 捕獲了一個新的 runningTotal 變量,該變量和 incrementByTen 中捕獲的變量沒有任何聯(lián)系:
let incrementBySeven = makeIncrementor(forIncrement: 7)incrementBySeven()// 返回的值為7incrementByTen()// 返回的值為40
注意:如果您閉包分配給一個類實例的屬性,并且該閉包通過指向該實例或其成員來捕獲了該實例,您將創(chuàng)建一個在閉包和實例間的強引用環(huán)。 Swift 使用捕獲列表來打破這種強引用環(huán)。更多信息,請參考 Strong Reference Cycles for Closures。

閉包是引用類型

上面的例子中,incrementBySeven 和 incrementByTen 是常量,但是這些常量指向的閉包仍然可以增加其捕獲的變量值。 這是因為函數(shù)和閉包都是引用類型。

無論您將函數(shù)/閉包賦值給一個常量還是變量,您實際上都是將常量/變量的值設(shè)置為對應(yīng)函數(shù)/閉包的引用。 上面的例子中,incrementByTen 指向閉包的引用是一個常量,而并非閉包內(nèi)容本身。

這也意味著如果您將閉包賦值給了兩個不同的常量/變量,兩個值都會指向同一個閉包:
純文本復(fù)制
let alsoIncrementByTen = incrementByTenalsoIncrementByTen()// 返回的值為50


發(fā)布手記

熱門詞條