指南:閉包(Closures)

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

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

  • 閉包表達(dá)式語法有如下一般形式:
{ (parameters) -> returnType in
    statements
}
  • 閉包表達(dá)式語法可以使用常量、變量和inout類型作為參數(shù),不能提供默認(rèn)值。也可以在參數(shù)列表的最后使用可變參數(shù)。元組也可以作為參數(shù)和返回值。
  • 如果可以推斷出參數(shù)和返回值的類型,這些類型都可以省略。返回箭頭(->)和圍繞在參數(shù)周圍的括號也可以被省略。
  • 單行表達(dá)式閉包可以通過省略return關(guān)鍵字來隱式返回單行表達(dá)式的結(jié)果。
  • Swift 自動為內(nèi)聯(lián)閉包提供了參數(shù)名稱縮寫功能,可以直接通過$0,$1,$2來順序調(diào)用閉包的參數(shù),以此類推。
  • 如果在閉包表達(dá)式中使用參數(shù)名稱縮寫,可以在閉包參數(shù)列表中省略對其的定義,并且對應(yīng)參數(shù)名稱縮寫的類型會通過函數(shù)類型進行推斷。in關(guān)鍵字也同樣可以被省略,因為此時閉包表達(dá)式完全由閉包函數(shù)體構(gòu)成。
  • Swift 的String類型定義了關(guān)于大于號(>)的字符串實現(xiàn),其作為一個函數(shù)接受兩個String類型的參數(shù)并返回Bool類型的值。而這正好與sort(_:)方法的參數(shù)需要的函數(shù)類型相符合。因此,您可以簡單地傳遞一個大于號,Swift 可以自動推斷出您想使用大于號的字符串函數(shù)實現(xiàn)。

在這里,>相當(dāng)于一個函數(shù)名,代表了一個函數(shù)(運算符函數(shù)),這里剛好湊上(類型符合)。這種情況是特例,不具有通用性。

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

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

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

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

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

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

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

reversed = names.sort(>)

在實際的使用中,是否需要這么省略,值得商量。畢竟可讀性還是首先要考慮的,少寫幾個字母,意義并不是很大。不過,這種利用類型推斷讓人偷懶的做法,還是挺有新意的。

尾隨閉包(Trailing Closures)

  • 如果需要將一個很長的閉包表達(dá)式作為最后一個參數(shù)傳遞給函數(shù),可以使用尾隨閉包來增強函數(shù)的可讀性。
  • 尾隨閉包是一個書寫在函數(shù)括號之后的閉包表達(dá)式,函數(shù)支持將其作為最后一個參數(shù)調(diào)用。
  • 如果函數(shù)只需要閉包表達(dá)式一個參數(shù),當(dāng)使用尾隨閉包時,甚至可以把()省略掉:
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]

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

寧可累一點,用if let 結(jié)構(gòu),也不要用!顯示表達(dá)對可選類型的處理過程。

捕獲值(Capturing Values)

  • 閉包可以在其被定義的上下文中捕獲常量或變量。
  • 嵌套函數(shù)可以捕獲其外部函數(shù)所有的參數(shù)以及定義的常量和變量。
  • Swift 會負(fù)責(zé)被捕獲變量的所有內(nèi)存管理工作,包括釋放不再需要的變量。
  • 如果將閉包賦值給一個類實例的屬性,并且該閉包通過訪問該實例或其成員而捕獲了該實例,將創(chuàng)建一個在閉包和該實例間的循環(huán)強引用。Swift 使用捕獲列表來打破這種循環(huán)強引用。

關(guān)于引用循環(huán),Swift的處理方式比較復(fù)雜,感覺不是很好

  • 函數(shù)和閉包都是引用類型。無論將函數(shù)或閉包賦值給一個常量還是變量,實際上都是將常量或變量的值設(shè)置為對應(yīng)函數(shù)或閉包的引用。
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementor() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementor
}
let incrementByTen = makeIncrementor(forIncrement: 10)
let incrementBySeven = makeIncrementor(forIncrement: 7)
let alsoIncrementByTen = incrementByTen

incrementByTen()
// 返回的值為10
incrementByTen()
// 返回的值為20
incrementByTen()
// 返回的值為30
incrementBySeven()
// 返回的值為7
incrementByTen()
// 返回的值為40
alsoIncrementByTen()
// 返回的值為50

感覺有點像靜態(tài)局部變量,還有類的實例變量隔離的作用。

非逃逸閉包(Nonescaping Closures)

  • 當(dāng)一個閉包作為參數(shù)傳到一個函數(shù)中,但是這個閉包在函數(shù)返回之后才被執(zhí)行,我們稱該閉包從函數(shù)中逃逸。

可以理解為異步,先離開函數(shù),等某件事完成之后,再來執(zhí)行閉包。Object-C中的Block基本上是這樣的

  • 當(dāng)定義接受閉包作為參數(shù)的函數(shù)時,在參數(shù)名之前標(biāo)注@noescape,用來指明這個閉包是不允許“逃逸”出這個函數(shù)的。閉包只能在函數(shù)體中被執(zhí)行,不能脫離函數(shù)體執(zhí)行.

可以理解為同步,先執(zhí)行完閉包,再離開函數(shù)。

func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) {
    closure()
}
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: () -> Void) {
    completionHandlers.append(completionHandler)
}
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"

閉包默認(rèn)是異步的(可逃逸的);加@noescape關(guān)鍵字,就是同步的(不可逃逸的)

自動閉包(Autoclosures)

  • 自動閉包是一種自動創(chuàng)建的閉包,用于包裝傳遞給函數(shù)作為參數(shù)的表達(dá)式。
  • 這種閉包不接受任何參數(shù),當(dāng)它被調(diào)用的時候,會返回被包裝在其中的表達(dá)式的值。
  • 這種便利語法能夠用一個普通的表達(dá)式來代替顯式的閉包,從而省略閉包的花括號。
  • 閉包能夠延遲求值,因為代碼段不會被執(zhí)行直到調(diào)用這個閉包為止。
  • 延遲求值對于那些有副作用(Side Effect)和代價昂貴的代碼來說是很有益處的,因為能控制代碼什么時候執(zhí)行。
  • 閉包是一種函數(shù),定義的時候是不執(zhí)行,只有在調(diào)用的時候才執(zhí)行,所有閉包有延遲求職功能。將表達(dá)式放在一個閉包之中,成為一個函數(shù),當(dāng)然就有了延遲求值功能
  • 自動閉包是閉包中一種簡略的表達(dá)形式,當(dāng)然也有閉包的延遲求值功能
  • 將閉包作為參數(shù)傳遞給函數(shù)時,能獲得同樣的延時求值行為。
  • 自動閉包用@autoclosure標(biāo)記,用在函數(shù)的參數(shù)中,一般不獨立存在
  • @autoclosure特性暗含了@noescape特性(同步)
  • 如果想讓這個閉包可以“逃逸”,則應(yīng)該使@autoclosure(escaping)特性.(異步)
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 is ["Alex", "Ewa", "Barry", "Daniella"]
func serveCustomer(customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serveCustomer( { customersInLine.removeAtIndex(0) } )
// prints "Now serving Alex!"
print(customersInLine.count)
// prints "3"

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serveCustomer(@autoclosure customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serveCustomer(customersInLine.removeAtIndex(0)) // 表達(dá)式自動轉(zhuǎn)閉包,不用謝{}了
// prints "Now serving Ewa!"
print(customersInLine.count)
// prints "2"

// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(@autoclosure(escaping) customerProvider: () -> String) {
    customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.removeAtIndex(0)) // 表達(dá)式自動轉(zhuǎn)閉包,不用謝{}了
collectCustomerProviders(customersInLine.removeAtIndex(0)) // 表達(dá)式自動轉(zhuǎn)閉包,不用謝{}了

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!"
  • 自動閉包是簡單函數(shù)作為其他函數(shù)參數(shù)的一種方便寫法
  • 過度使用autoclosures會讓你的代碼變得難以理解。上下文和函數(shù)名應(yīng)該能夠清晰地表明求值是被延遲執(zhí)行的。
  • 為了省一對{},引入@autoclosure關(guān)鍵字,有必要嗎?實際使用中不如顯示加上{},表示這里是一個閉包,增加可讀性
  • @autoclosure特性暗含了@noescape特性(同步);而普通閉包{}暗含了escape特性(異步);這里剛好是相反的,理解上有困難嗎?
  • 函數(shù)調(diào)用的時候直接寫表達(dá)式,并且可以省略{},默認(rèn)獲得@noescape特性(同步),這大概就是自動閉包@autoclosure的好處吧。有必要嗎?----用閉包{},不用管同步還是異步,讓系統(tǒng)處理,是否更好?
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 閉包是自包含的函數(shù)代碼塊,可以在代碼中被傳遞和使用。Swift 中的閉包與 C 和 Objective-C 中的代...
    窮人家的孩紙閱讀 1,811評論 1 5
  • 閉包是自包含的函數(shù)代碼塊,可以在代碼中被傳遞和使用。Swift 中的閉包與 C 和 Objective-C 中的代...
    莽原奔馬668閱讀 1,958評論 2 12
  • 本章將會介紹 閉包表達(dá)式尾隨閉包值捕獲閉包是引用類型逃逸閉包自動閉包枚舉語法使用Switch語句匹配枚舉值關(guān)聯(lián)值原...
    寒橋閱讀 1,629評論 0 3
  • “我有一個大夢想,啦啦啦啦啦,吃遍天下的美食,啦啦啦啦啦!”蘇小墨站在新生接待處哼著小曲。沈妍看著來來往往的小學(xué)弟...
    Amolli閱讀 431評論 0 0
  • 我左邊的座位坐下了兩個女人和一個小女孩。 被小女孩叫媽媽的女人單坐一邊,被叫作媽媽的女人稱呼好久不見的女人手?jǐn)埿∨?..
    PhilipYoung閱讀 704評論 0 1

友情鏈接更多精彩內(nèi)容