swift——閉包

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

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

全局和嵌套函數(shù)實(shí)際上也是特殊的閉包更舞,閉包采取如下三種形式之一:

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

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

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

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

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

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

sort 方法(The Sort Method)

Swift 標(biāo)準(zhǔn)庫(kù)提供了名為sort的方法膀哲,會(huì)根據(jù)提供的用于排序的閉包函數(shù)將已知類型數(shù)組中的值進(jìn)行排序往产。一旦排序完成,sort(_:)方法會(huì)返回一個(gè)與原數(shù)組大小相同,包含同類型元素且元素已正確排序的新數(shù)組某宪。原數(shù)組不會(huì)被sort(_:)方法修改仿村。

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

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

sort(_:)方法接受一個(gè)閉包,該閉包函數(shù)需要傳入與數(shù)組元素類型相同的兩個(gè)值兴喂,并返回一個(gè)布爾類型值來(lái)表明當(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ù)的一種方式是撰寫一個(gè)符合其類型要求的普通函數(shù)云矫,并將其作為sort(_:)方法的參數(shù)傳入:

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

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

然而,這是一個(gè)相當(dāng)冗長(zhǎng)的方式麻车,本質(zhì)上只是寫了一個(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 = names.sort({ (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ù)和返回值類型都寫在大括號(hào)內(nèi)链瓦,而不是大括號(hào)外拆魏。

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

由于這個(gè)閉包的函數(shù)體部分如此短,以至于可以將其改寫成一行代碼:

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

該例中sort(_:)方法的整體調(diào)用保持不變贴膘,一對(duì)圓括號(hào)仍然包裹住了方法的整個(gè)參數(shù)卖子。然而,參數(shù)現(xiàn)在變成了內(nèi)聯(lián)閉包刑峡。

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

因?yàn)榕判蜷]包函數(shù)是作為sort(_:)方法的參數(shù)傳入的洋闽,Swift 可以推斷其參數(shù)和返回值的類型。sort(_:)方法被一個(gè)字符串?dāng)?shù)組調(diào)用氛琢,因此其參數(shù)必須是(String, String) -> Bool類型的函數(shù)喊递。這意味著(String, String)Bool類型并不需要作為閉包表達(dá)式定義的一部分随闪。因?yàn)樗械念愋投伎梢员徽_推斷阳似,返回箭頭(->)和圍繞在參數(shù)周圍的括號(hào)也可以被省略:

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

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

盡管如此畜吊,仍然可以明確寫出有著完整格式的閉包。如果完整格式的閉包能夠提高代碼的可讀性户矢,則可以采用完整格式的閉包玲献。而在sort(_:)方法這個(gè)例子里,閉包的目的就是排序梯浪。由于這個(gè)閉包是為了處理字符串?dāng)?shù)組的排序捌年,因此讀者能夠推測(cè)出這個(gè)閉包是用于字符串處理的。

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

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

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

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

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

Swift 自動(dòng)為內(nèi)聯(lián)閉包提供了參數(shù)名稱縮寫功能励堡,可以直接通過$0谷丸,$1$2來(lái)順序調(diào)用閉包的參數(shù)念秧,以此類推淤井。

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

reversed = names.sort( {
    $0 > $1
} )

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

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

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

reversed = names.sort(>)

尾隨閉包(Trailing Closures)

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

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

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

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

作為sort(_:)方法參數(shù)的字符串排序閉包可以改寫為:

reversed = names.sort() { $0 > $1 }

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

reversed = names.sort { $0 > $1 }

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

當(dāng)提供給數(shù)組的閉包應(yīng)用于每個(gè)數(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)換為字符串?dāng)?shù)組的整型數(shù)組巷挥。

現(xiàn)在可以通過傳遞一個(gè)尾隨閉包給numbersmap(_:)方法來(lái)創(chuàng)建對(duì)應(yīng)的字符串版本數(shù)組:

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)榭梢酝ㄟ^要映射的數(shù)組類型進(jìn)行推斷雏节。

在該例中,閉包number參數(shù)被聲明為一個(gè)變量參數(shù)高职,因此可以在閉包函數(shù)體內(nèi)對(duì)其進(jìn)行修改钩乍,而不用再定義一個(gè)新的局部變量并將number的值賦值給它。閉包表達(dá)式指定了返回類型為String怔锌,以表明存儲(chǔ)映射值的新數(shù)組類型為String寥粹。

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

注意
字典digitNames下標(biāo)后跟著一個(gè)嘆號(hào)(!)涝涤,因?yàn)樽值湎聵?biāo)返回一個(gè)可選值(optional value),表明該鍵不存在時(shí)會(huì)查找失敗岛杀。在上例中阔拳,由于可以確定number % 10總是digitNames字典的有效下標(biāo),因此嘆號(hào)可以用于強(qiáng)制解包 (force-unwrap) 存儲(chǔ)在下標(biāo)的可選類型的返回值中的String類型的值类嗤。

digitNames字典中獲取的字符串被添加到output的前部糊肠,逆序建立了一個(gè)字符串版本的數(shù)字。(在表達(dá)式number % 10中遗锣,如果number16砰苍,則返回6荆隘,58返回8桑逝,510返回0击费。)

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

整個(gè)過程重復(fù)進(jìn)行经伙,直到number /= 100扶叉,這時(shí)閉包會(huì)將字符串output返回,而map(_:)方法則會(huì)將字符串添加到所映射的數(shù)組中帕膜。

在上面的例子中枣氧,通過尾隨閉包語(yǔ)法,優(yōu)雅地在函數(shù)后封裝了閉包的具體功能垮刹,而不再需要將整個(gè)閉包包裹在map(_:)方法的括號(hào)內(nèi)达吞。

捕獲值(Capturing Values)

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

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

舉個(gè)例子,這有一個(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類型的值交排。

makeIncrementer(forIncrement:)函數(shù)定義了一個(gè)初始值為0的整型變量runningTotal划滋,用來(lái)存儲(chǔ)當(dāng)前跑步總數(shù)。該值通過incrementor返回埃篓。

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

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

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

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

incrementer()函數(shù)并沒有任何參數(shù)委刘,但是在函數(shù)體內(nèi)訪問了runningTotalamount變量丧没。這是因?yàn)樗鼜耐鈬瘮?shù)捕獲了runningTotalamount變量的引用。捕獲引用保證了runningTotalamount變量在調(diào)用完makeIncrementer后不會(huì)消失锡移,并且保證了在下一次執(zhí)行incrementer函數(shù)時(shí)呕童,runningTotal依舊存在。

注意
為了優(yōu)化淆珊,如果一個(gè)值是不可變的夺饲,Swift 可能會(huì)改為捕獲并保存一份對(duì)值的拷貝。
Swift 也會(huì)負(fù)責(zé)被捕獲變量的所有內(nèi)存管理工作,包括釋放不再需要的變量往声。

下面是一個(gè)使用makeIncrementor的例子:

let incrementByTen = makeIncrementor(forIncrement: 10)

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

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

如果創(chuàng)建了另一個(gè)incrementor烁挟,它會(huì)有屬于它自己的一個(gè)全新婴洼、獨(dú)立的runningTotal變量的引用:

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

再次調(diào)用原來(lái)的incrementByTen會(huì)在原來(lái)的變量runningTotal上繼續(xù)增加值,該變量和incrementBySeven中捕獲的變量沒有任何聯(lián)系:

incrementByTen()
// 返回的值為40

注意
如果將閉包賦值給一個(gè)類實(shí)例的屬性撼嗓,并且該閉包通過訪問該實(shí)例或其成員而捕獲了該實(shí)例柬采,將創(chuàng)建一個(gè)在閉包和該實(shí)例間的循環(huán)強(qiáng)引用。Swift 使用捕獲列表來(lái)打破這種循環(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

非逃逸閉包(Nonescaping Closures)

當(dāng)一個(gè)閉包作為參數(shù)傳到一個(gè)函數(shù)中,但是這個(gè)閉包在函數(shù)返回之后才被執(zhí)行寓娩,我們稱該閉包從函數(shù)中逃逸叛氨。定義接受閉包作為參數(shù)的函數(shù)時(shí),可以在參數(shù)名之前標(biāo)注@noescape棘伴,用來(lái)指明這個(gè)閉包是不允許“逃逸”出這個(gè)函數(shù)的寞埠。將閉包標(biāo)注@noescape能使編譯器知道這個(gè)閉包的生命周期(譯者注:閉包只能在函數(shù)體中被執(zhí)行,不能脫離函數(shù)體執(zhí)行焊夸,所以編譯器明確知道運(yùn)行時(shí)的上下文)仁连,從而可以進(jìn)行一些比較激進(jìn)的優(yōu)化。

func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) {
    closure()
}

舉個(gè)例子淳地,sort(_:)方法接受一個(gè)用來(lái)進(jìn)行元素比較的閉包作為參數(shù)怖糊。這個(gè)參數(shù)被標(biāo)注了@noescape帅容,因?yàn)樗_保自己在排序結(jié)束之后就沒用了颇象。

一種能使閉包“逃逸”出函數(shù)的方法是,將這個(gè)閉包保存在一個(gè)函數(shù)外部定義的變量中并徘。舉個(gè)例子遣钳,很多啟動(dòng)異步操作的函數(shù)接受一個(gè)閉包參數(shù)作為 completion handler。這類函數(shù)會(huì)在異步操作開始之后立刻返回麦乞,但是閉包直到異步操作結(jié)束后才會(huì)被調(diào)用蕴茴。在這種情況下劝评,閉包需要“逃逸”出函數(shù),因?yàn)殚]包需要在函數(shù)返回之后被調(diào)用倦淀。例如:

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: () -> Void) {
    completionHandlers.append(completionHandler)
}

someFunctionWithEscapingClosure(_:)函數(shù)接受一個(gè)閉包作為參數(shù)蒋畜,該閉包被添加到一個(gè)函數(shù)外定義的數(shù)組中。如果試圖將這個(gè)參數(shù)標(biāo)注為@noescape撞叽,將會(huì)獲得一個(gè)編譯錯(cuò)誤姻成。

將閉包標(biāo)注為@noescape使得能在閉包中隱式地引用self

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNoescapeClosure { x = 200 }
    }
}
 
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// prints "200"
 
completionHandlers.first?()
print(instance.x)
// prints "100"

自動(dòng)閉包(Autoclosures)

自動(dòng)閉包是一種自動(dòng)創(chuàng)建的閉包愿棋,用于包裝傳遞給函數(shù)作為參數(shù)的表達(dá)式科展。這種閉包不接受任何參數(shù),當(dāng)它被調(diào)用的時(shí)候糠雨,會(huì)返回被包裝在其中的表達(dá)式的值才睹。這種便利語(yǔ)法讓你能夠用一個(gè)普通的表達(dá)式來(lái)代替顯式的閉包,從而省略閉包的花括號(hào)甘邀。

我們經(jīng)常會(huì)調(diào)用一個(gè)接受閉包作為參數(shù)的函數(shù)琅攘,但是很少實(shí)現(xiàn)那樣的函數(shù)。舉個(gè)例子來(lái)說松邪,assert(condition:message:file:line:)函數(shù)接受閉包作為它的condition參數(shù)和message參數(shù)乎澄;它的condition參數(shù)僅會(huì)在 debug 模式下被求值,它的message參數(shù)僅當(dāng)condition參數(shù)為false時(shí)被計(jì)算求值测摔。

自動(dòng)閉包能夠延遲求值置济,因?yàn)榇a段不會(huì)被執(zhí)行直到調(diào)用這個(gè)閉包。延遲求值對(duì)于那些有副作用(Side Effect)和代價(jià)昂貴的代碼來(lái)說是很有益處的锋八,因?yàn)槟芸刂拼a什么時(shí)候執(zhí)行浙于。下面的代碼展示了閉包如何延時(shí)求值。

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// prints "5"
 
let customerProvider = { customersInLine.removeAtIndex(0) }
print(customersInLine.count)
// prints "5"
 
print("Now serving \(customerProvider())!")
// prints "Now serving Chris!"
print(customersInLine.count)
// prints "4"

盡管在閉包的代碼中挟纱,customersInLine的第一個(gè)元素被移除了羞酗,不過在閉包被調(diào)用之前,這個(gè)元素是不會(huì)被移除的紊服。如果這個(gè)閉包永遠(yuǎn)不被調(diào)用檀轨,那么在閉包里面的表達(dá)式將永遠(yuǎn)不會(huì)執(zhí)行,那意味著列表中的元素永遠(yuǎn)不會(huì)被移除欺嗤。請(qǐng)注意参萄,customerProvider的類型不是String,而是() -> String煎饼,一個(gè)沒有參數(shù)且返回值為String的函數(shù)讹挎。

將閉包作為參數(shù)傳遞給函數(shù)時(shí),能獲得同樣的延時(shí)求值行為。

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serveCustomer(customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serveCustomer( { customersInLine.removeAtIndex(0) } )
// prints "Now serving Alex!"

serveCustomer(_:)接受一個(gè)返回顧客名字的顯式的閉包筒溃。下面這個(gè)版本的serveCustomer(_:)完成了相同的操作马篮,不過它并沒有接受一個(gè)顯式的閉包,而是通過將參數(shù)標(biāo)記為@autoclosure來(lái)接收一個(gè)自動(dòng)閉包×保現(xiàn)在可以將該函數(shù)當(dāng)做接受String類型參數(shù)的函數(shù)來(lái)調(diào)用浑测。customerProvider參數(shù)將自動(dòng)轉(zhuǎn)化為一個(gè)閉包,因?yàn)樵搮?shù)被標(biāo)記了@autoclosure特性歪玲。

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serveCustomer(@autoclosure customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serveCustomer(customersInLine.removeAtIndex(0))
// prints "Now serving Ewa!"

注意
過度使用autoclosures會(huì)讓代碼變得難以理解尽爆。上下文和函數(shù)名應(yīng)該能夠清晰地表明求值是被延遲執(zhí)行的。

@autoclosure特性暗含了@noescape特性读慎。如果想讓這個(gè)閉包可以“逃逸”漱贱,則應(yīng)該使用@autoclosure(escaping)特性.

// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(@autoclosure(escaping) customerProvider: () -> String) {
    customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.removeAtIndex(0))
collectCustomerProviders(customersInLine.removeAtIndex(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!"

在上面的代碼中,collectCustomerProviders(_:)函數(shù)并沒有調(diào)用傳入的customerProvider閉包夭委,而是將閉包追加到了customerProviders數(shù)組中幅狮。這個(gè)數(shù)組定義在函數(shù)作用域范圍外,這意味著數(shù)組內(nèi)的閉包將會(huì)在函數(shù)返回之后被調(diào)用株灸。因此崇摄,customerProvider參數(shù)必須允許“逃逸”出函數(shù)作用域。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末慌烧,一起剝皮案震驚了整個(gè)濱河市逐抑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌屹蚊,老刑警劉巖厕氨,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異汹粤,居然都是意外死亡命斧,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門嘱兼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)国葬,“玉大人,你說我怎么就攤上這事芹壕』闼模” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵踢涌,是天一觀的道長(zhǎng)通孽。 經(jīng)常有香客問我,道長(zhǎng)斯嚎,這世上最難降的妖魔是什么利虫? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任挨厚,我火速辦了婚禮堡僻,結(jié)果婚禮上糠惫,老公的妹妹穿的比我還像新娘。我一直安慰自己钉疫,他們只是感情好硼讽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著牲阁,像睡著了一般固阁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上城菊,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天备燃,我揣著相機(jī)與錄音,去河邊找鬼凌唬。 笑死并齐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的客税。 我是一名探鬼主播况褪,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼更耻!你這毒婦竟也來(lái)了测垛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤秧均,失蹤者是張志新(化名)和其女友劉穎食侮,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體目胡,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疙描,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了讶隐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片起胰。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖巫延,靈堂內(nèi)的尸體忽然破棺而出效五,到底是詐尸還是另有隱情,我是刑警寧澤炉峰,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布畏妖,位于F島的核電站,受9級(jí)特大地震影響疼阔,放射性物質(zhì)發(fā)生泄漏戒劫。R本人自食惡果不足惜半夷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望迅细。 院中可真熱鬧巫橄,春花似錦、人聲如沸茵典。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)统阿。三九已至彩倚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扶平,已是汗流浹背帆离。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留结澄,地道東北人哥谷。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像概而,于是被迫代替她去往敵國(guó)和親呼巷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 閉包是功能性自包含模塊赎瑰,可以在代碼中被傳遞和使用王悍。Swift中的閉包與 C 和 Objective-C中的 blo...
    AirZilong閱讀 349評(píng)論 0 2
  • 閉包可以從定義它們的上下文中捕獲和存儲(chǔ)對(duì)任何常量和變量的引用。 這被稱為關(guān)閉這些常量和變量餐曼。 Swift處理所有的...
    Joker_King閱讀 593評(píng)論 0 2
  • Swift 中的閉包是自包含的函數(shù)代碼塊压储,可以在代碼中被傳遞和使用。類似于OC中的Block以及其他函數(shù)的匿名函數(shù)...
    喬克_叔叔閱讀 522評(píng)論 1 3
  • * 閉包 是自包含的函數(shù)代碼塊源譬,可以在代碼中被傳遞和使用集惋。swift中的閉包和Objective-C中的代碼塊(b...
    EndEvent閱讀 856評(píng)論 4 8
  • 閉包是自包含的代碼塊,可以在代碼中被傳遞和使用踩娘。Swift中的閉包與C和Objective-C中代碼塊(block...
    Raaaaamsey閱讀 847評(píng)論 0 2