7、【Swift】閉包

閉包-Closures

  • 自包含的函數代碼塊

  • 與 C 和 Objective-C 中的代碼塊(blocks))以及其他語言的匿名函數(Lambdas)比較相似

  • 閉包會 捕獲 + 存儲常量和變量的引用(稱為包裹常量和變量)

  • Swift會管理捕獲過程的內存操作

  • 全局和嵌套函數是特殊的閉包

  • 閉包的三種形式:

    • 全局函數是一個有名字但不會捕獲任何值的閉包;
    • 內嵌函數是一個有名字且能從其上層函數捕獲值的閉包;
    • 閉包表達式是一個輕量級語法所寫的可以捕獲其上下文中常量或變量值的沒有名字的閉包。

閉包表達式

  • 函數嵌套,代碼塊式的定義和命名形式,十分方便
  • 閉包表達式是一種內聯閉包

排序方法

  • sorted(by:) 的方法/函數:接受一個包含排序表達式的閉包參數,返回排好序的新數組
  • 原數組不會被 sorted(by:) 方法修改
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
  • 排序閉包函數類型需為 (String, String) -> Bool
func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}

// 將函數當作閉包傳參
var reversedNames = names.sorted(by: backward)
// reversedNames 為 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

閉包表達式語法

  • 閉包表達式語法
{ (parameters) -> return type in
    statements
}
  • 參數 可以是 in-out 參數

  • 參數不能設定默認值

  • 命名了可變參數,也可以使用此可變參數

  • 元組也可以作為參數和返回值

  • backward(_:_:) 函數對應的閉包表達式版本的代碼

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})
  • 內聯閉包類型與 backward(_:_:) 函數類型聲明相同

  • 關鍵字 in:參數 + 返回值類型 in(分界線) 函數體

  • 函數體短的,可簡寫成一行代碼

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
  • 參數現在變成了內聯閉包

根據上下文推斷類型

  • 省略不寫:參數類型 + 返回值類型

    • 字符串數組調用sorted(by:) 方法 -> 參數是字符串類型
    • 排序 -> 返回值是布爾類型
  • 省略后的寫法

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
  • 當把閉包作為行內閉包表達式傳遞給函數或方法時,形式參數類型和返回類型都可以被推斷出來

單表達式閉包的隱式返回

  • 省略不寫: return 返回值
    • 單表達式 -> 隱式返回

參數名稱縮寫

  • 省略不寫:參數名稱
    • Swift會自動把閉包的參數名稱命名為:0 ,1 , $2······
    • in 關鍵字也省略不寫
  • 省略后寫法
reversedNames = names.sorted(by: { $0 > $1 } )

運算符方法/函數 - Operator Methods

  • 省略不寫:參數

    • Swift已經為String類型實現了大于號( >)的運算符方法/函數
    @inlinable public static func > (lhs: String, rhs: String) -> Bool    
    
  • 更多關于運算符方法的內容請查看 運算符方法

尾隨閉包

  • 尾隨閉包場景:防止閉包太長,無法一行書寫,不再需要將整個閉包包裹在 funName(_:) 方法的括號內
  • 函數的最后一個參數為:閉包表達式
  • 尾隨閉包,不用寫參數標簽
func someFunctionThatTakesAClosure(closure: () -> Void) {
    // 函數體部分
}


// 以下是不使用尾隨閉包進行函數調用
someFunctionThatTakesAClosure(closure: {
    // 閉包主體部分
})


// 以下是使用尾隨閉包進行函數調用
someFunctionThatTakesAClosure() {
    // 閉包主體部分
}
  • 函數的參數只有一個閉包表達式時,可把圓括號 () 省略掉
reversedNames = names.sorted { $0 > $1 }    
  • 例子:Array 類型的 map(_:) 方法,
    • 該閉包函數會為數組中的每一個元素調用一次,并返回該元素所映射的值的新數組
    • 映射方式和返回值類型由閉包來推斷
  • 創建一個整型和英文字符串的映射關系字典
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 = ""
    repeat {
        output = digitNames[number % 10]! + output
        number /= 10
    } while number > 0
    return output
}
// strings 常量被推斷為字符串類型數組,即 [String]
// 其值為 ["OneSix", "FiveEight", "FiveOneZero"]
  • 根據數組類型,可確定number的類型

digitNames 的下標緊跟著一個感嘆號( ! ),因為字典下標返回一個可選值,表明即使該 key 不存在也不會查找失敗

值捕獲

  • 場景:捕獲常量或變量,延長生命周期。
  • 定義常量或變量的作用域消失,閉包內部依然可引用和修改這些值
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

值在閉包內不被修改,捕獲一份副本/拷貝即可

Swift會自動處理值捕獲的管理內存

let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
// 返回的值為10
incrementByTen()
// 返回的值為20
incrementByTen()
// 返回的值為30
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// 返回的值為7
incrementByTen()
// 返回的值為40

閉包是引用類型

  • 函數和閉包都是引用類型
  • 將閉包賦值給了兩個不同的常量或變量,兩個值都會指向同一個閉包
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// 返回的值為50

逃逸閉包 - Escaping Closures

  • 閉包作為實參傳給函數的時候
  • 非逃逸閉包:閉包在函數中(返回前)運行(閉包的引用計數在進入函數和退出函數時保持不變)
  • 逃逸閉包:閉包在函數返回/結束后運行(稱該閉包從函數中逃逸,逃逸閉包生命周期長于相關函數函數退出時,逃逸閉包的引用仍然被其他對象持有)
  • 閉包參數默認是非逃逸類型。如果需要其逃逸類型的閉包,可以使用關鍵字@escaping
  • 場景:
    • 異步調用:如果需要調度隊列中異步調用閉包,這個隊列會持有閉包的引用,至于什么時候調用閉包,或者閉包什么時候運行結束都是不可預知的
    • 存儲:需要存儲閉包作為屬性,全局變量或者其他類型做稍后使用。
  • 閉包逃逸條件:閉包被函數外部的變量引用/保存
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}
  • someFunctionWithEscapingClosure(_:) 函數接受一個閉包作為參數,該閉包被添加到一個函數外定義的數組中(不將這個參數標記為 @escaping,就會得到一個編譯錯誤)
  • 為了提醒處理循環引用:

    • 逃逸閉包要顯式調用self
    • 非逃逸閉包可隱式調用self
func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}


let instance = SomeClass()
instance.doSomething()
print(instance.x)
// 打印出“200”


completionHandlers.first?()
print(instance.x)
// 打印出“100”
class SomeOtherClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { [self] in x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}
  • 如果 self 是結構體或者枚舉的實例,可隱式地引用 self
  • 總之,當 self 的類型是結構體或者枚舉時,逃逸閉包不能捕獲mutable的 self 引用
  • 如同 結構體和枚舉是值類型 中描述的那樣,結構體和枚舉不允許共享可修改性。
struct SomeStruct {
    var x = 10
    mutating func doSomething() {
        someFunctionWithNonescapingClosure { x = 200 }  // Ok
        someFunctionWithEscapingClosure { x = 100 }     // Error
    }
}
  • someFunctionWithEscapingClosure會報錯,違反了逃逸閉包不能捕獲mutable結構體類型的self

自動閉包

  • 使用場景:延時執行, 只傳一個表達式,不傳顯式閉包,省略閉包的花括號
  • 下面的代碼展示了閉包如何延遲求值
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]// 客戶名稱-數組
print(customersInLine.count)
// 打印出“5”

let customerProvider = { customersInLine.remove(at: 0) }// 類型不是 `String`,而是 `() -> String`
print(customersInLine.count)
// 打印出“5”

print("Now serving \(customerProvider())!")// 返回被移除的元素
// 打印出“Now serving Chris!”
print(customersInLine.count)
// 打印出“4”
  • 如果閉包不調用,第一個元素永遠不會被移除

  • customerProvider 的類型不是 String,而是 () -> String,一個沒有參數且返回值為 String 的函數

  • 把閉包作參數傳給函數,實現延遲處理

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// 打印出“Now serving Alex!”
  • 不接受顯式閉包,將函數的閉包形參標記為 @autoclosure來接收一個自動閉包
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// 打印“Now serving Ewa!”

為了代碼可讀性,避免過度使用autoclosure。

  • 自動閉包 + 逃逸閉包。同時使用 @autoclosure@escaping 屬性
// customersInLine i= ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))


print("Collected \(customerProviders.count) closures.")
// 打印“Collected 2 closures.”
for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")
}
// 打印“Now serving Barry!”
// 打印“Now serving Daniella!”
  • @autoclosure 只支持 () -> T 格式的參數
  • @autoclosure 并非只支持最后1個參數
  • 空合并運算符 ?? 使用了 @autoclosure 技術
  • 有@autoclosure、無@autoclosure,構成了函數重載
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

  • 閉包是自包含的函數代碼塊,可以在代碼中被傳遞和使用。Swift 中的閉包與 C 和 Objective-C 中的代...
    CoderLGL閱讀 281評論 0 0
  • swift 中的閉包和JS中的匿名函數很像,但是它還有一些其它的特性,其中包括一些看起來很怪異的語法糖,寫法看起來...
    JamesSawyer閱讀 333評論 0 0
  • Closures:閉包 閉包是獨立的函數塊,可以在代碼中傳遞和使用。Swift中的閉包類似于C和Objective...
    沐靈洛閱讀 432評論 0 2
  • 級別: ★☆☆☆☆標簽:「iOS」「Swift 5.1」「閉包」「逃逸閉包」「尾隨閉包」作者: 沐靈洛審校: Qi...
    QiShare閱讀 785評論 0 5
  • http://wiki.jikexueyuan.com/project/swift/[http://wiki.ji...
    xmb閱讀 185評論 0 2