在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ 閉包(Closures)
方法 - Methods
關(guān)于 Swift
下標(biāo)腳本(Subscripts)
類和結(jié)構(gòu)體
類型轉(zhuǎn)換(Type Casting)
控制流
析構(gòu)過(guò)程(Deinitialization)
集合類型 (Collection Types)
構(gòu)造過(guò)程(Initialization)
Optional Chaining
枚舉(Enumerations)
自動(dòng)引用計(jì)數(shù)
繼承(Inheritance)
擴(kuò)展(Extensions)
泛型
字符串和字符(Strings and Characters)
函數(shù)(Functions)
高級(jí)運(yùn)算符
訪問(wèn)控制
基本運(yùn)算符
嵌套類型
閉包(Closures)
協(xié)議
屬性 (Properties)

閉包(Closures)

閉包是自包含的函數(shù)代碼塊,可以在代碼中被傳遞和使用。 Swift 中的閉包與 C 和 Objective-C 中的代碼塊(blocks)以及其他一些編程語(yǔ)言中的 lambdas 函數(shù)比較相似。

閉包可以捕獲和存儲(chǔ)其所在上下文中任意常量和變量的引用。 這就是所謂的閉合并包裹著這些常量和變量,俗稱閉包。Swift 會(huì)為您管理在捕獲過(guò)程中涉及到的所有內(nèi)存操作。

注意: 如果您不熟悉捕獲(capturing)這個(gè)概念也不用擔(dān)心,您可以在 值捕獲 章節(jié)對(duì)其進(jìn)行詳細(xì)了解。

函數(shù) 章節(jié)中介紹的全局和嵌套函數(shù)實(shí)際上也是特殊的閉包,閉包采取如下三種形式之一:

  • 全局函數(shù)是一個(gè)有名字但不會(huì)捕獲任何值的閉包
  • 嵌套函數(shù)是一個(gè)有名字并可以捕獲其封閉函數(shù)域內(nèi)值的閉包
  • 閉包表達(dá)式是一個(gè)利用輕量級(jí)語(yǔ)法所寫(xiě)的可以捕獲其上下文中變量或常量值的匿名閉包

Swift 的閉包表達(dá)式擁有簡(jiǎn)潔的風(fēng)格,并鼓勵(lì)在常見(jiàn)場(chǎng)景中進(jìn)行語(yǔ)法優(yōu)化,主要優(yōu)化如下:

  • 利用上下文推斷參數(shù)和返回值類型
  • 隱式返回單表達(dá)式閉包,即單表達(dá)式閉包可以省略return關(guān)鍵字
  • 參數(shù)名稱縮寫(xiě)
  • 尾隨(Trailing)閉包語(yǔ)法

閉包表達(dá)式(Closure Expressions)

嵌套函數(shù) 是一個(gè)在較復(fù)雜函數(shù)中方便進(jìn)行命名和定義自包含代碼模塊的方式。當(dāng)然,有時(shí)候撰寫(xiě)小巧的沒(méi)有完整定義和命名的類函數(shù)結(jié)構(gòu)也是很有用處的,尤其是在您處理一些函數(shù)并需要將另外一些函數(shù)作為該函數(shù)的參數(shù)時(shí)。

閉包表達(dá)式是一種利用簡(jiǎn)潔語(yǔ)法構(gòu)建內(nèi)聯(lián)閉包的方式。 閉包表達(dá)式提供了一些語(yǔ)法優(yōu)化,使得撰寫(xiě)閉包變得簡(jiǎn)單明了。 下面閉包表達(dá)式的例子通過(guò)使用幾次迭代展示了sorted函數(shù)定義和語(yǔ)法優(yōu)化的方式。 每一次迭代都用更簡(jiǎn)潔的方式描述了相同的功能。

sorted 函數(shù)(The Sorted Function)

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

下面的閉包表達(dá)式示例使用sorted函數(shù)對(duì)一個(gè)String類型的數(shù)組進(jìn)行字母逆序排序,以下是初始數(shù)組值:

    let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

sorted函數(shù)需要傳入兩個(gè)參數(shù):

  • 已知類型的數(shù)組
  • 閉包函數(shù),該閉包函數(shù)需要傳入與數(shù)組類型相同的兩個(gè)值,并返回一個(gè)布爾類型值來(lái)告訴sorted函數(shù)當(dāng)排序結(jié)束后傳入的第一個(gè)參數(shù)排在第二個(gè)參數(shù)前面還是后面。如果第一個(gè)參數(shù)值出現(xiàn)在第二個(gè)參數(shù)值前面,排序閉包函數(shù)需要返回true,反之返回false

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

提供排序閉包函數(shù)的一種方式是撰寫(xiě)一個(gè)符合其類型要求的普通函數(shù),并將其作為sort函數(shù)的第二個(gè)參數(shù)傳入:

    func backwards(s1: String, s2: String) -> Bool {
        return s1 > s2
    }
    var reversed = sorted(names, backwards)
    // reversed 為 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

如果第一個(gè)字符串 (s1) 大于第二個(gè)字符串 (s2),backwards函數(shù)返回true,表示在新的數(shù)組中s1應(yīng)該出現(xiàn)在s2前。 對(duì)于字符串中的字符來(lái)說(shuō),“大于” 表示 “按照字母順序較晚出現(xiàn)”。 這意味著字母"B"大于字母"A",字符串"Tom"大于字符串"Tim"。 其將進(jìn)行字母逆序排序,"Barry"將會(huì)排在"Alex"之前。

然而,這是一個(gè)相當(dāng)冗長(zhǎng)的方式,本質(zhì)上只是寫(xiě)了一個(gè)單表達(dá)式函數(shù) (a > b)。 在下面的例子中,利用閉合表達(dá)式語(yǔ)法可以更好的構(gòu)造一個(gè)內(nèi)聯(lián)排序閉包。

閉包表達(dá)式語(yǔ)法(Closure Expression Syntax)

閉包表達(dá)式語(yǔ)法有如下一般形式:

    { (parameters) -> returnType in
        statements
    }

閉包表達(dá)式語(yǔ)法可以使用常量、變量和inout類型作為參數(shù),不提供默認(rèn)值。 也可以在參數(shù)列表的最后使用可變參數(shù)。 元組也可以作為參數(shù)和返回值。

下面的例子展示了之前backwards函數(shù)對(duì)應(yīng)的閉包表達(dá)式版本的代碼:

    reversed = sorted(names, { (s1: String, s2: String) -> Bool in
        return s1 > s2
    })

需要注意的是內(nèi)聯(lián)閉包參數(shù)和返回值類型聲明與backwards函數(shù)類型聲明相同。 在這兩種方式中,都寫(xiě)成了(s1: String, s2: String) -> Bool。 然而在內(nèi)聯(lián)閉包表達(dá)式中,函數(shù)和返回值類型都寫(xiě)在大括號(hào)內(nèi),而不是大括號(hào)外。

閉包的函數(shù)體部分由關(guān)鍵字in引入。 該關(guān)鍵字表示閉包的參數(shù)和返回值類型定義已經(jīng)完成,閉包函數(shù)體即將開(kāi)始。

因?yàn)檫@個(gè)閉包的函數(shù)體部分如此短以至于可以將其改寫(xiě)成一行代碼:

    reversed = sorted(names, { (s1: String, s2: String) -> Bool in return s1 > s2 } )

這說(shuō)明sorted函數(shù)的整體調(diào)用保持不變,一對(duì)圓括號(hào)仍然包裹住了函數(shù)中整個(gè)參數(shù)集合。而其中一個(gè)參數(shù)現(xiàn)在變成了內(nèi)聯(lián)閉包(相比于backwards版本的代碼)。

根據(jù)上下文推斷類型(Inferring Type From Context)

因?yàn)榕判蜷]包函數(shù)是作為sorted函數(shù)的參數(shù)進(jìn)行傳入的,Swift可以推斷其參數(shù)和返回值的類型。 sorted期望第二個(gè)參數(shù)是類型為(String, String) -> Bool的函數(shù),因此實(shí)際上String,StringBool類型并不需要作為閉包表達(dá)式定義中的一部分。 因?yàn)樗械念愋投伎梢员徽_推斷,返回箭頭 (->) 和圍繞在參數(shù)周圍的括號(hào)也可以被省略:

    reversed = sorted(names, { s1, s2 in return s1 > s2 } )

實(shí)際上任何情況下,通過(guò)內(nèi)聯(lián)閉包表達(dá)式構(gòu)造的閉包作為參數(shù)傳遞給函數(shù)時(shí),都可以推斷出閉包的參數(shù)和返回值類型,這意味著您幾乎不需要利用完整格式構(gòu)造任何內(nèi)聯(lián)閉包。

單表達(dá)式閉包隱式返回(Implicit Return From Single-Expression Clossures)

單行表達(dá)式閉包可以通過(guò)隱藏return關(guān)鍵字來(lái)隱式返回單行表達(dá)式的結(jié)果,如上版本的例子可以改寫(xiě)為:

    reversed = sorted(names, { s1, s2 in s1 > s2 } )

在這個(gè)例子中,sorted函數(shù)的第二個(gè)參數(shù)函數(shù)類型明確了閉包必須返回一個(gè)Bool類型值。 因?yàn)殚]包函數(shù)體只包含了一個(gè)單一表達(dá)式 (s1 > s2),該表達(dá)式返回Bool類型值,因此這里沒(méi)有歧義,return關(guān)鍵字可以省略。

參數(shù)名稱縮寫(xiě)(Shorthand Argument Names)

Swift 自動(dòng)為內(nèi)聯(lián)函數(shù)提供了參數(shù)名稱縮寫(xiě)功能,您可以直接通過(guò)$0,$1,$2來(lái)順序調(diào)用閉包的參數(shù)。

如果您在閉包表達(dá)式中使用參數(shù)名稱縮寫(xiě),您可以在閉包參數(shù)列表中省略對(duì)其的定義,并且對(duì)應(yīng)參數(shù)名稱縮寫(xiě)的類型會(huì)通過(guò)函數(shù)類型進(jìn)行推斷。 in關(guān)鍵字也同樣可以被省略,因?yàn)榇藭r(shí)閉包表達(dá)式完全由閉包函數(shù)體構(gòu)成:

    reversed = sorted(names, { $0 > $1 } )

在這個(gè)例子中,$0$1表示閉包中第一個(gè)和第二個(gè)String類型的參數(shù)。

運(yùn)算符函數(shù)(Operator Functions)

實(shí)際上還有一種更簡(jiǎn)短的方式來(lái)撰寫(xiě)上面例子中的閉包表達(dá)式。 Swift 的String類型定義了關(guān)于大于號(hào) (>) 的字符串實(shí)現(xiàn),其作為一個(gè)函數(shù)接受兩個(gè)String類型的參數(shù)并返回Bool類型的值。 而這正好與sorted函數(shù)的第二個(gè)參數(shù)需要的函數(shù)類型相符合。 因此,您可以簡(jiǎn)單地傳遞一個(gè)大于號(hào),Swift可以自動(dòng)推斷出您想使用大于號(hào)的字符串函數(shù)實(shí)現(xiàn):

    reversed = sorted(names, >)

更多關(guān)于運(yùn)算符表達(dá)式的內(nèi)容請(qǐng)查看 運(yùn)算符函數(shù)。

尾隨閉包(Trailing Closures)

如果您需要將一個(gè)很長(zhǎng)的閉包表達(dá)式作為最后一個(gè)參數(shù)傳遞給函數(shù),可以使用尾隨閉包來(lái)增強(qiáng)函數(shù)的可讀性。 尾隨閉包是一個(gè)書(shū)寫(xiě)在函數(shù)括號(hào)之后的閉包表達(dá)式,函數(shù)支持將其作為最后一個(gè)參數(shù)調(diào)用。

    func someFunctionThatTakesAClosure(closure: () -> ()) {
        // 函數(shù)體部分
    }

    // 以下是不使用尾隨閉包進(jìn)行函數(shù)調(diào)用
    someFunctionThatTakesAClosure({
        // 閉包主體部分
    })

    // 以下是使用尾隨閉包進(jìn)行函數(shù)調(diào)用
    someFunctionThatTakesAClosure() {
        // 閉包主體部分
    }

注意: 如果函數(shù)只需要閉包表達(dá)式一個(gè)參數(shù),當(dāng)您使用尾隨閉包時(shí),您甚至可以把()省略掉。

在上例中作為sorted函數(shù)參數(shù)的字符串排序閉包可以改寫(xiě)為:

    reversed = sorted(names) { $0 > $1 }

當(dāng)閉包非常長(zhǎng)以至于不能在一行中進(jìn)行書(shū)寫(xiě)時(shí),尾隨閉包變得非常有用。 舉例來(lái)說(shuō),Swift 的Array類型有一個(gè)map方法,其獲取一個(gè)閉包表達(dá)式作為其唯一參數(shù)。 數(shù)組中的每一個(gè)元素調(diào)用一次該閉包函數(shù),并返回該元素所映射的值(也可以是不同類型的值)。 具體的映射方式和返回值類型由閉包來(lái)指定。

當(dāng)提供給數(shù)組閉包函數(shù)后,map方法將返回一個(gè)新的數(shù)組,數(shù)組中包含了與原數(shù)組一一對(duì)應(yīng)的映射后的值。

下例介紹了如何在map方法中使用尾隨閉包將Int類型數(shù)組[16,58,510]轉(zhuǎn)換為包含對(duì)應(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)建了一個(gè)數(shù)字位和它們名字映射的英文版本字典。 同時(shí)定義了一個(gè)準(zhǔn)備轉(zhuǎn)換為字符串的整型數(shù)組。

您現(xiàn)在可以通過(guò)傳遞一個(gè)尾隨閉包給numbersmap方法來(lái)創(chuàng)建對(duì)應(yīng)的字符串版本數(shù)組。 需要注意的時(shí)調(diào)用numbers.map不需要在map后面包含任何括號(hào),因?yàn)槠渲恍枰獋鬟f閉包表達(dá)式這一個(gè)參數(shù),并且該閉包表達(dá)式參數(shù)通過(guò)尾隨方式進(jìn)行撰寫(xiě):

    let strings = numbers.map {
        (var number) -> String in
        var output = ""
        while number > 0 {
            output = digitNames[number % 10]! + output
            number /= 10
        }
        return output
    }
    // strings 常量被推斷為字符串類型數(shù)組,即 String[]
    // 其值為 ["OneSix", "FiveEight", "FiveOneZero"]

map在數(shù)組中為每一個(gè)元素調(diào)用了閉包表達(dá)式。 您不需要指定閉包的輸入?yún)?shù)number的類型,因?yàn)榭梢酝ㄟ^(guò)要映射的數(shù)組類型進(jìn)行推斷。

閉包number參數(shù)被聲明為一個(gè)變量參數(shù)(變量的具體描述請(qǐng)參看常量參數(shù)和變量參數(shù)),因此可以在閉包函數(shù)體內(nèi)對(duì)其進(jìn)行修改。閉包表達(dá)式制定了返回類型為String,以表明存儲(chǔ)映射值的新數(shù)組類型為String。

閉包表達(dá)式在每次被調(diào)用的時(shí)候創(chuàng)建了一個(gè)字符串并返回。 其使用求余運(yùn)算符 (number % 10) 計(jì)算最后一位數(shù)字并利用digitNames字典獲取所映射的字符串。

注意: 字典digitNames下標(biāo)后跟著一個(gè)嘆號(hào) (!),因?yàn)樽值湎聵?biāo)返回一個(gè)可選值 (optional value),表明即使該 key 不存在也不會(huì)查找失敗。 在上例中,它保證了number % 10可以總是作為一個(gè)digitNames字典的有效下標(biāo) key。 因此嘆號(hào)可以用于強(qiáng)制解析 (force-unwrap) 存儲(chǔ)在可選下標(biāo)項(xiàng)中的String類型值。

digitNames字典中獲取的字符串被添加到輸出的前部,逆序建立了一個(gè)字符串版本的數(shù)字。 (在表達(dá)式number % 10中,如果number為16,則返回6,58返回8,510返回0)。

number變量之后除以10。 因?yàn)槠涫钦麛?shù),在計(jì)算過(guò)程中未除盡部分被忽略。 因此 16變成了1,58變成了5,510變成了51。

整個(gè)過(guò)程重復(fù)進(jìn)行,直到number /= 10為0,這時(shí)閉包會(huì)將字符串輸出,而map函數(shù)則會(huì)將字符串添加到所映射的數(shù)組中。

上例中尾隨閉包語(yǔ)法在函數(shù)后整潔封裝了具體的閉包功能,而不再需要將整個(gè)閉包包裹在map函數(shù)的括號(hào)內(nèi)。

捕獲值(Capturing Values)

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

Swift最簡(jiǎn)單的閉包形式是嵌套函數(shù),也就是定義在其他函數(shù)的函數(shù)體內(nèi)的函數(shù)。 嵌套函數(shù)可以捕獲其外部函數(shù)所有的參數(shù)以及定義的常量和變量。

下例為一個(gè)叫做makeIncrementor的函數(shù),其包含了一個(gè)叫做incrementor嵌套函數(shù)。 嵌套函數(shù)incrementor從上下文中捕獲了兩個(gè)值,runningTotalamount。 之后makeIncrementorincrementor作為閉包返回。 每次調(diào)用incrementor時(shí),其會(huì)以amount作為增量增加runningTotal的值。

    func makeIncrementor(forIncrement amount: Int) -> () -> Int {
        var runningTotal = 0
        func incrementor() -> Int {
            runningTotal += amount
            return runningTotal
        }
        return incrementor
    }

makeIncrementor返回類型為() -> Int。 這意味著其返回的是一個(gè)函數(shù),而不是一個(gè)簡(jiǎn)單類型值。 該函數(shù)在每次調(diào)用時(shí)不接受參數(shù)只返回一個(gè)Int類型的值。 關(guān)于函數(shù)返回其他函數(shù)的內(nèi)容,請(qǐng)查看函數(shù)類型作為返回類型

makeIncrementor函數(shù)定義了一個(gè)整型變量runningTotal(初始為0) 用來(lái)存儲(chǔ)當(dāng)前跑步總數(shù)。 該值通過(guò)incrementor返回。

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

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

如果我們單獨(dú)看這個(gè)函數(shù),會(huì)發(fā)現(xiàn)看上去不同尋常:

    func incrementor() -> Int {
        runningTotal += amount
        return runningTotal
    }

incrementor函數(shù)并沒(méi)有獲取任何參數(shù),但是在函數(shù)體內(nèi)訪問(wèn)了runningTotalamount變量。這是因?yàn)槠渫ㄟ^(guò)捕獲在包含它的函數(shù)體內(nèi)已經(jīng)存在的runningTotalamount變量而實(shí)現(xiàn)。

由于沒(méi)有修改amount變量,incrementor實(shí)際上捕獲并存儲(chǔ)了該變量的一個(gè)副本,而該副本隨著incrementor一同被存儲(chǔ)。

然而,因?yàn)槊看握{(diào)用該函數(shù)的時(shí)候都會(huì)修改runningTotal的值,incrementor捕獲了當(dāng)前runningTotal變量的引用,而不是僅僅復(fù)制該變量的初始值。捕獲一個(gè)引用保證了當(dāng)makeIncrementor結(jié)束時(shí)候并不會(huì)消失,也保證了當(dāng)下一次執(zhí)行incrementor函數(shù)時(shí),runningTotal可以繼續(xù)增加。

注意: Swift 會(huì)決定捕獲引用還是拷貝值。 您不需要標(biāo)注amount或者runningTotal來(lái)聲明在嵌入的incrementor函數(shù)中的使用方式。 Swift 同時(shí)也處理runingTotal變量的內(nèi)存管理操作,如果不再被incrementor函數(shù)使用,則會(huì)被清除。

下面代碼為一個(gè)使用makeIncrementor的例子:

    let incrementByTen = makeIncrementor(forIncrement: 10)

該例子定義了一個(gè)叫做incrementByTen的常量,該常量指向一個(gè)每次調(diào)用會(huì)加10的incrementor函數(shù)。 調(diào)用這個(gè)函數(shù)多次可以得到以下結(jié)果:

    incrementByTen()
    // 返回的值為10
    incrementByTen()
    // 返回的值為20
    incrementByTen()
    // 返回的值為30

如果您創(chuàng)建了另一個(gè)incrementor,其會(huì)有一個(gè)屬于自己的獨(dú)立的runningTotal變量的引用。 下面的例子中,incrementBySevne捕獲了一個(gè)新的runningTotal變量,該變量和incrementByTen中捕獲的變量沒(méi)有任何聯(lián)系:

    let incrementBySeven = makeIncrementor(forIncrement: 7)
    incrementBySeven()
    // 返回的值為7
    incrementByTen()
    // 返回的值為40

注意: 如果您將閉包賦值給一個(gè)類實(shí)例的屬性,并且該閉包通過(guò)指向該實(shí)例或其成員來(lái)捕獲了該實(shí)例,您將創(chuàng)建一個(gè)在閉包和實(shí)例間的強(qiáng)引用環(huán)。 Swift 使用捕獲列表來(lái)打破這種強(qiáng)引用環(huán)。更多信息,請(qǐng)參考 閉包引起的循環(huán)強(qiáng)引用。

閉包是引用類型(Closures Are Reference Types)

上面的例子中,incrementBySevenincrementByTen是常量,但是這些常量指向的閉包仍然可以增加其捕獲的變量值。 這是因?yàn)楹瘮?shù)和閉包都是引用類型。

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

這也意味著如果您將閉包賦值給了兩個(gè)不同的常量/變量,兩個(gè)值都會(huì)指向同一個(gè)閉包:

    let alsoIncrementByTen = incrementByTen
    alsoIncrementByTen()
    // 返回的值為50