代碼練習(xí)閉包舟茶。
//
// main.swift
// SwiftLearn9-閉包
//
// Created by iOS on 2018/4/17.
// Copyright ? 2018年 weiman. All rights reserved.
//
import Foundation
print("Hello, World!")
/** 一川陆、
閉包是自包含的代碼塊,可以在代碼中被傳遞和使用售貌。
swift的閉包與C和OC的Block以及其他一些語言的匿名函數(shù)比較相似。
*/
/*
閉包采取如下三種形式之一:
1.全局函數(shù)是一個有名字但不會捕獲任何值的閉包。
2.嵌套函數(shù)是一個有名字并可以捕獲其封閉函數(shù)域內(nèi)值的閉包
3.閉包表達(dá)式是一個利用輕量級語法所寫的可以捕獲上下文中變量或者常量值的匿名閉包
*/
/*
swift閉包表達(dá)式擁有簡潔的風(fēng)格碗啄,并鼓勵優(yōu)化笔宿。主要優(yōu)化如下:
1.利用上下文推斷參數(shù)和返回值類型
2.隱式返回單表達(dá)式閉包犁钟,即單表達(dá)式閉包可以省略關(guān)鍵字return
3.參數(shù)名稱縮寫
4.尾隨閉包語法
*/
do {
// 數(shù)組排序
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func backward(s1: String, s2: String) -> Bool {
return s1 > s2
}
// 名字按照字母倒序排列
var reversedNames = names.sorted(by: backward)
print("使用函數(shù)棱诱。\n \(reversedNames)")
// 使用閉包
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
print("使用閉包。\n \(reversedNames)")
/*
閉包的函數(shù)體部分由關(guān)鍵字in引入涝动。該關(guān)鍵字表示閉包的參數(shù)和返回值類型定義已經(jīng)完成军俊,閉包函數(shù)體即將開始。
*/
//閉包優(yōu)化1: 根據(jù)上下文推斷類型
// 根據(jù)數(shù)組內(nèi)的元素類型可以推斷出參數(shù)的類型捧存,所以粪躬,參數(shù)類型可以省略
reversedNames = names.sorted(by: { (s1, s2) -> Bool in return s1 > s2 })
print("根據(jù)上下文推斷類型。\n \(reversedNames)")
//閉包優(yōu)化2: 單表達(dá)式閉包隱式返回
//單行表達(dá)式可以省略return關(guān)鍵字
reversedNames = names.sorted(by: { (s1, s2) -> Bool in s1 > s2 })
print("單表達(dá)式閉包隱式返回\n \(reversedNames)")
//閉包優(yōu)化3: 參數(shù)名稱縮寫
/*Swift 自動為內(nèi)聯(lián)閉包提供了參數(shù)名稱縮寫功能昔穴,你可以直接通過 $0镰官,$1,$2 來順序調(diào)用閉包的參數(shù)吗货,以此類推.
如果你在閉包表達(dá)式中使用參數(shù)名稱縮寫泳唠,你可以在閉包定義中省略參數(shù)列表,并且對應(yīng)參數(shù)名稱縮寫的類型會通過函數(shù)類型進(jìn)行推斷宙搬。in關(guān)鍵字也同樣可以被省略笨腥,因?yàn)榇藭r閉包表達(dá)式完全由閉包函數(shù)體構(gòu)成:
*/
reversedNames = names.sorted(by: { $0 > $1 })
print("參數(shù)名稱縮寫\n \(reversedNames)")
//閉包優(yōu)化4: 運(yùn)算符方法
/*
Swift 的 String 類型定義了關(guān)于大于號(>)的字符串實(shí)現(xiàn),其作為一個函數(shù)接受兩個 String 類型的參數(shù)并返回 Bool 類型的值勇垛。而這正好與 sorted(by:) 方法的參數(shù)需要的函數(shù)類型相符合脖母。因此,你可以簡單地傳遞一個大于號闲孤,Swift 可以自動推斷出你想使用大于號的字符串函數(shù)實(shí)現(xiàn):
*/
reversedNames = names.sorted(by: >)
print("運(yùn)算符方法\n \(reversedNames)")
}
// 二谆级、尾隨閉包
// 如果你需要將一個很長的閉包表達(dá)式作為最后一個參數(shù)傳遞給函數(shù),可以使用尾隨閉包來增強(qiáng)函數(shù)的可讀性讼积。
// 尾隨閉包肥照,顧名思義就是閉包作為函數(shù)的最后一個參數(shù)。
do {
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var reversedNames = names.sorted(){ $0 > $1 }
print("尾隨閉包\n \(reversedNames)")
//如果閉包表達(dá)式是函數(shù)或方法的唯一參數(shù)勤众,則當(dāng)你使用尾隨閉包時舆绎,你甚至可以把 () 省略
reversedNames = names.sorted { $0 > $1 }
print("尾隨閉包簡化\n \(reversedNames)")
}
/*
使用尾隨閉包將 Int 類型數(shù)組 [16, 58, 510] 轉(zhuǎn)換為包含對應(yīng) String 類型的值的數(shù)組["OneSix", "FiveEight", "FiveOneZero"]
*/
do {
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
}
print("---- \(strings)")
}
/*
三、值捕獲
閉包可以在其被定義的上下文中捕獲常量或者變量们颜。即使定義這些常量和變量的原作用域已經(jīng)不存在吕朵,閉包仍然可以在閉包函數(shù)體內(nèi)引用和修改這些值。
Swift 中掌桩,可以捕獲值的閉包的最簡單形式是嵌套函數(shù)边锁,也就是定義在其他函數(shù)的函數(shù)體內(nèi)的函數(shù)。嵌套函數(shù)可以捕獲其外部函數(shù)所有的參數(shù)以及定義的常量和變量波岛。
舉個例子茅坛,這有一個叫做 makeIncrementer 的函數(shù),其包含了一個叫做 incrementer 的嵌套函數(shù)。嵌套函數(shù) incrementer() 從上下文中捕獲了兩個值贡蓖,runningTotal 和 amount曹鸠。捕獲這些值之后,makeIncrementer 將 incrementer 作為閉包返回斥铺。每次調(diào)用 incrementer 時彻桃,其會以 amount 作為增量增加 runningTotal 的值。
*/
func makeIncrementer(forIncrement amount: Int) -> ()->Int{
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
print("-----1----- \(incrementByTen())")
print("-----2----- \(incrementByTen())")
print("-----3----- \(incrementByTen())")
print("-----4----- \(incrementByTen())")
/*
結(jié)果是:
-----1----- 10
-----2----- 20
-----3----- 30
-----4----- 40
*/
/*
解釋:
捕獲引用保證了 runningTotal 和 amount 變量在調(diào)用完 makeIncrementer 后不會消失晾蜘,并且保證了在下一次執(zhí)行 incrementer 函數(shù)時邻眷,runningTotal 依舊存在。
注意 為了優(yōu)化剔交,如果一個值不會被閉包改變肆饶,或者在閉包創(chuàng)建后不會改變,Swift 可能會改為捕獲并保存一份對值的拷貝岖常。 Swift 也會負(fù)責(zé)被捕獲變量的所有內(nèi)存管理工作驯镊,包括釋放不再需要的變量。
*/
/*
如果你創(chuàng)建了另一個 incrementer竭鞍,它會有屬于自己的引用板惑,指向一個全新、獨(dú)立的 runningTotal 變量:
*/
let incrementBySeven = makeIncrementer(forIncrement: 7)
print("--aa--- \(incrementBySeven())")
/*
注意:
如果你將閉包賦值給一個類實(shí)例的屬性偎快,并且該閉包通過訪問該實(shí)例或其成員而捕獲了該實(shí)例冯乘,你將在閉包和該實(shí)例間創(chuàng)建一個循環(huán)強(qiáng)引用。Swift 使用捕獲列表來打破這種循環(huán)強(qiáng)引用
*/
/*
四滨砍、閉包是引用類型
上面的例子中往湿,incrementBySeven 和 incrementByTen 都是常量妖异,但是這些常量指向的閉包仍然可以增加其捕獲的變量的值惋戏。這是因?yàn)楹瘮?shù)和閉包都是引用類型。
這也意味著如果你將閉包賦值給了兩個不同的常量或變量他膳,兩個值都會指向同一個閉包响逢。
*/
let alsoIncrementByTen = incrementByTen
print("--bb-- \(alsoIncrementByTen())")
/*
打印結(jié)果:
--bb-- 50
*/
/*
五、逃逸閉包
當(dāng)一個閉包作為參數(shù)傳到一個函數(shù)中棕孙,但是這個閉包在函數(shù)返回之后才被執(zhí)行舔亭,我們稱該閉包從函數(shù)中逃逸。
當(dāng)你定義接受閉包作為參數(shù)的函數(shù)時蟀俊,你可以在參數(shù)名之前標(biāo)注 @escaping钦铺,用來指明這個閉包是允許“逃逸”出這個函數(shù)的。
*/
// 一個數(shù)組肢预,數(shù)組的元素是無參數(shù)矛洞,無返回值的函數(shù)
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
//將一個閉包標(biāo)記為 @escaping 意味著你必須在閉包中顯式地引用 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"
/*
六烫映、自動閉包
自動閉包是一種自動創(chuàng)建的閉包沼本,用于包裝傳遞給函數(shù)作為參數(shù)的表達(dá)式噩峦。
這種閉包不接受任何參數(shù),當(dāng)它被調(diào)用的時候抽兆,會返回被包裝在其中的表達(dá)式的值识补。這種便利語法讓你能夠省略閉包的花括號,用一個普通的表達(dá)式來代替顯式的閉包辫红。
*/
do {
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
let customerProvider = { customersInLine.remove(at : 0) }
print("customersInLine: \(customersInLine.count)")
print("customerProvider = \(customerProvider())")
print("customersInLine: \(customersInLine.count)")
/*
打印結(jié)果:
5
customersInLine: 5
customerProvider = Chris
customersInLine: 4
由此可以看出凭涂,在調(diào)用customerProvider之前,花括號中的語句體都沒有執(zhí)行贴妻,直到customerProvider調(diào)用才執(zhí)行导盅。
*/
//將閉包作為參數(shù)傳遞給函數(shù)時,你能獲得同樣的延時求值行為揍瑟。
func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// 輸出:Now serving Alex!
/*
注意
過度使用 autoclosures 會讓你的代碼變得難以理解白翻。上下文和函數(shù)名應(yīng)該能夠清晰地表明求值是被延遲執(zhí)行的。
*/
}
// 個人認(rèn)為绢片,代碼的書寫應(yīng)該盡量的邏輯清晰滤馍,易讀,過多的使用閉包或許會讓代碼過于晦澀難懂底循。