Swift筆記——閉包

每次看閉包的時(shí)候,都覺得挺簡單的。不就是Objective-C中的block嘛,但是長時(shí)間不看,再來用的時(shí)候有些地方老是印象模糊,特別是一些省略寫法。好記性不如爛筆頭,還是決定整理整理記下來。

閉包描述

排序方法

swift中有個(gè)排序的方法sorted(by:),它會根據(jù)你提供的閉包,對數(shù)組已知類型的元素進(jìn)行排序。原數(shù)組:

let nameArr = ["zhangsan", "lisi", "wangwu", "zhujiu"]

因?yàn)檫@個(gè)數(shù)組的元素類型是字符串,所以需要傳的閉包類型是(String, String) -> Bool寫個(gè)這樣類型的函數(shù),并編譯,就得出了排序之后的新數(shù)組了。

let nameArr = ["zhangsan", "lisi", "wangwu", "zhujiu"]
func nameSortFunction(_ n1: String, _ n2: String) -> Bool {
    return n1 > n2
}
let newNameArr = nameArr.sorted(by: nameSortFunction)
print(nameArr)
print(newNameArr)
// ["zhangsan", "lisi", "wangwu", "zhujiu"]
// ["zhujiu", "zhangsan", "wangwu", "lisi"]
閉包語法

閉包的一般格式:

 { (parameters) -> return type in
    statements
}

上面的例子可以閉包的形式替代:

let newNameArr = nameArr.sorted(by: {(n1: String, n2: String) -> Bool in
    return n1 > n2
})

閉包的內(nèi)容是以in開始的,in標(biāo)志著閉包的參數(shù)和返參已經(jīng)定義完成,準(zhǔn)備開始功能實(shí)現(xiàn)了。因?yàn)檫@個(gè)閉包的內(nèi)容很少,所以甚至可以寫成一行。

let newNameArr = nameArr.sorted(by: {(n1: String, n2: String) -> Bool in return n1 > n2 })
類型推斷

因?yàn)殚]包是作為一個(gè)參數(shù)傳入一個(gè)方法,所以swift可以推斷它參數(shù)的類型。上面的例子中,對一個(gè)元素為字符串的數(shù)組排序,所以傳入的閉包類型必須為(String, String) -> Bool,這就意味著(String, String)Bool類型不用寫。因?yàn)樗械念愋投伎梢酝茢?,所以箭頭和圓括號也可以刪掉。當(dāng)然如果繼續(xù)寫類型的話,在swift中是鼓勵(lì)的,這樣可以方便代碼的讀者閱讀。

let newNameArr = nameArr.sorted(by: {n1, n2 in return n1 > n2 })
單行表達(dá)式的含蓄返回

如果閉包只有一句表達(dá)式,那么它會自動(dòng)加上返回的功能,所以return可以省略。

let newNameArr = nameArr.sorted(by: {n1, n2 in n1 > n2 })
速寫參數(shù)名

swift會自動(dòng)對內(nèi)聯(lián)閉包提供速寫參數(shù)名,比如$0$1、$2等等。如果用這種方式的話,參數(shù)列表和in也可以刪掉。

let newNameArr = nameArr.sorted(by: { $0 > $1 })
算子

上面這個(gè)例子甚至還有一種更簡單的方式,就是在閉包中直接傳入一個(gè)大于的算子,swift可以推斷你想要它針對字符串特定的實(shí)現(xiàn)。

let newNameArr = nameArr.sorted(by: > )

尾隨閉包

如果閉包是作為一個(gè)函數(shù)的最后一個(gè)參數(shù)傳入,并且這個(gè)閉包很長,那么可以使用尾隨閉包替代。尾隨閉包是寫在函數(shù)的調(diào)用括號外面,雖然它還是函數(shù)的一個(gè)參數(shù)。上面的例子就可以使用尾隨閉包。
使用前:

let newNameArr = nameArr.sorted(by: { $0 > $1 })

使用后:

let newNameArr = nameArr.sorted(){ $0 > $1 }

如果閉包是這個(gè)函數(shù)或方法的唯一參數(shù),而且又是一個(gè)尾隨閉包的話,那么連括號都可以刪掉。

let newNameArr = nameArr.sorted{ $0 > $1 }

復(fù)雜一點(diǎn)的例子。用map(_:)把一個(gè)Int類型的數(shù)組轉(zhuǎn)化成String類型的數(shù)組。

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)
// ["OneSix", "FiveEight", "FiveOneZero"]

在這個(gè)閉包里,不用明確每個(gè)參數(shù)的類型是Int,因?yàn)殚]包會自動(dòng)推斷。

捕捉變量

閉包可以捕獲周圍上下文中的常量和變量?,F(xiàn)在有一個(gè)嵌套閉包。函數(shù)makeIncrementer包含一個(gè)嵌套函數(shù)incrementer。嵌套函數(shù)incrementer捕獲兩個(gè)變量,runningTotalrunningTotal。通過捕獲兩個(gè)變量之后,每次調(diào)用函數(shù)makeIncrementer,嵌套函數(shù)incrementer都會讓runningTotalrunningTotal,并以閉包的形式作為函數(shù)makeIncrementer的返參返回。

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

定義一個(gè)變量接收函數(shù)makeIncrementer返回的閉包,并連續(xù)調(diào)用。

let incrementByTen = makeIncrementer(forIncrement: 10)
print(incrementByTen())
// 10
print(incrementByTen())
// 20
print(incrementByTen())
// 30

在函數(shù)makeIncrementer里定義了一個(gè)變量runningTotal用來儲存累計(jì)增加的總數(shù),初始化為0。調(diào)用makeIncrementer返回來一個(gè)() -> Int類型的閉包。第一次調(diào)incrementByTen時(shí),runningTotal為0,amount為10,調(diào)用后,返回10(0+10),同時(shí)runningTotal變?yōu)?0,以此類推。如果再調(diào)用一次函數(shù)makeIncrementer生成一個(gè)新的閉包,并調(diào)用該閉包的話,那么該閉包捕獲的runningTotal的初始值仍然為0,這時(shí)候再重新調(diào)用incrementByTen,它的runningTotal仍然為30,不受新生成的閉包的影響。

let incrementBySeven = makeIncrementer(forIncrement: 7)
print(incrementBySeven())
// 7
print(incrementByTen())
// 40

閉包是指針類型

定義一個(gè)常量指向閉包時(shí),只是定義一個(gè)指向閉包的常量指針,而不是閉包本身是常量。比如上例中,如果定義另一個(gè)常量指向incrementByTen,它其實(shí)指向的是同一個(gè)閉包。

let alsoIncrementByTen = incrementByTen
print(alsoIncrementByTen())
// 50

逃逸閉包

當(dāng)一個(gè)閉包作為一個(gè)參數(shù)傳入一個(gè)函數(shù),并且這個(gè)閉包是在這個(gè)參數(shù)執(zhí)行返回之后調(diào)用,那么這個(gè)閉包就是一個(gè)逃逸閉包,需要在參數(shù)類型前面用@escaping字段修飾,標(biāo)明這個(gè)閉包可以逃逸,否則會報(bào)編譯錯(cuò)誤。

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

使用@escaping標(biāo)記閉包,意味著你在閉包內(nèi)部必須明確指向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

自動(dòng)閉包

自動(dòng)閉包用@autoclosure修飾,使用自動(dòng)閉包可以延緩及時(shí)性要求不那么高的閉包的編譯。但是使用自動(dòng)閉包會增加代碼的閱讀難度。

Overusing autoclosures can make your code hard to understand. The context and function name should make it clear that evaluation is being deferred.

備注

Xcode版本:9.3.1
Swift版本:4.1
官方文檔:Closures

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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