首先回顧基礎(chǔ),在Swift中:
1.函數(shù)可以被賦值給變量冕杠,也可以作為另一個函數(shù)的輸入?yún)?shù)秽荞,或者另一個函數(shù)的返回值來使用
這點不必多說
2.函數(shù)能夠捕獲存在于其局部作用域之外的變量
func counterFunc() -> (Int) -> String {
var counter = 0
func innerFunc(i: Int) -> String {
counter += i // counter is captured return "running total: \(counter)"
}
return innerFunc
}
let f = counterFunc()
f(3) // running total: 3
f(4) // running total: 7
let g = counterFunc()
g(2) // running total: 2
g(2) // running total: 4
f(2) // running total: 9
在結(jié)構(gòu)體和類提到過,counter 將存在于堆上而非棧上幼东。多次調(diào)用 innerFunc颊糜,并且看到 running total 的輸出在增加
可以看到f(3)和f(4)的count在持續(xù)累加,
再次調(diào)用 counterFunc() 函數(shù)凑阶,將會生成并 “捕獲” 新的 counter 變量,也就是g(2)
并且不會影響第一個函數(shù)f(2)還可以繼續(xù)累加
將這些函數(shù)以及它們所捕獲的變量想象為一個類的實例瓦胎,這個類擁有一個單一的方法以及一些成員變量 (這里的被捕獲的變量),這種一個函數(shù)和它所捕獲的變量環(huán)境組合起來被稱為閉包,也就是f和g.
3.有兩種方法可以創(chuàng)建函數(shù)芬萍,一種是使用 func 關(guān)鍵字,另一種是 { }搔啊。在 Swift 中担忧,后一種被稱為閉包表達式
let doubler = { (i:Int) -> Int in return i * 2} //或者簡化為let doubler = {$0 * 2}
[1, 2, 3, 4].map(doubler)
這就是閉包表達式,相對于func doubler(i:Int) -> Int
{ (i:Int) -> Int in i * 2}是函數(shù)的字面量,沒有名字,不過被賦值給了變量doubler.
這里兩個doubler是等價的,包括命名空間.
閉包指的是一個函數(shù)以及被它所捕獲的所有變量的組合;而使用 { } 來創(chuàng)建的函數(shù)被稱為閉包表達式
一:通過優(yōu)化排序方法深入理解閉包
Swift一共有四個排序的方法:不可變版本的 sorted(by:) 和可變的 sort(by:),以及兩者在待排序?qū)ο笞袷谻omparable 時進行升序排序的無參數(shù)版本坯癣。對于最常?的情況,這個無參數(shù)的sorted() 就是你所需要的最欠。如果你需要用不同于默認升序的順序進行排序的話示罗,可以提供一個排序函數(shù):
let myArray = [3, 1, 2]
myArray.sorted() // [1, 2, 3]
myArray.sorted(by: >) // [3, 2, 1]
就算待排序的元素不遵守 Comparable,但是只要有 < 操作符芝硬,你就可以使用這個方法來進行排序蚜点,比如可選值就是一個例子:
var numberStrings = [(2, "two"), (1, "one"), (3, "three")]
numberStrings.sort(by: <)
numberStrings // [(1, "one"), (2, "two"), (3, "three")]
如果用 Foundation 進行排序,會遇到一?串不同的選項拌阴,F(xiàn)oundation 中有接受 selector绍绘、block 或者函數(shù)指針作為比較方式的排序方法,或者你也可以傳入一個排序描述符的數(shù)組迟赃。Swift 也可以使用任意的比較函數(shù) (也就是那些返回NSComparisonResult 的函數(shù)) 來對集合進行排序.
Foundation所有這些都提供了大量的靈活度和各式功能陪拘,但是代價是使排序變得相當復(fù)雜.
沒有一個選項可以讓我能 “只需要基于默認順序進行一個常規(guī)的排序”。Foundation 中那些接受 block 作為比較斷言的方法纤壁,在實質(zhì)上和 Swift 的 sorted(by:)方法是一樣的左刽;其他的像是接受排序描述符數(shù)組的方法,則利用了 Objective-C 的動態(tài)特性酌媒,它們是十分靈活和強大 (但是是弱類型) 的 API欠痴,它們不能被直接移植到 Swift 中,對于 selector 和動態(tài)派發(fā)的支持在 Swift 中依然有效迄靠,但是 Swift 標準庫更傾向于使用基于函數(shù)的方式.
為了對比Foundation,頂一個一個繼承NSObject的Person類,
@objcMembers作用是給所有的屬性和方法增加@objc
以及一個數(shù)組
@objcMembers
final class Person: NSObject {
let frst: String
let last: String
let yearOfBirth: Int
init(frst: String, last: String, yearOfBirth: Int) {
self.frst = frst
self.last = last
self.yearOfBirth = yearOfBirth
}
}
let people = [ Person(frst: "Emily", last: "Young", yearOfBirth: 2002),
Person(frst: "David", last: "Gray", yearOfBirth: 1991),
Person(frst: "Robert", last: "Barnes", yearOfBirth: 1985),
Person(frst: "Ava", last: "Barnes", yearOfBirth: 2000),
Person(frst: "Joanne", last: "Miller", yearOfBirth: 1994),
Person(frst: "Ava", last: "Barnes", yearOfBirth: 1998),
]
目標是對這個數(shù)組進行排序,規(guī)則是:
先按照姓排序喇辽,再按照名排序掌挚,最后是出生年份。
我們可以使用 NSSortDescriptor 對象來描述如何排序?qū)ο笃凶桑ㄟ^它可以表達出各個排序標準 .
使用 localizedStandardCompare 來進行遵循區(qū)域設(shè)置的排序,這樣的話中文名字也可以排序了.
首先按照三個規(guī)則分別創(chuàng)建排序描述符NSSortDescriptor
let lastDescriptor = NSSortDescriptor(key: #keyPath(Person.last), ascending: true, selector: #selector(NSString.localizedStandardCompare(_:)))
let frstDescriptor = NSSortDescriptor(key: #keyPath(Person.frst), ascending: true, selector: #selector(NSString.localizedStandardCompare(_:)))
let yearDescriptor = NSSortDescriptor(key: #keyPath(Person.yearOfBirth), ascending: true)
然后對數(shù)組進行排序,sortedArray(using:) 可以接受多個排序描述符,先使用第一個描述符吠式,并檢查其結(jié)果,如果兩個
元素在第一個描述符下相同,那么它將使用第二個描述符,以此類推
let descriptors = [lastDescriptor, frstDescriptor, yearDescriptor] (people as NSArray).sortedArray(using: descriptors)
/*[Ava Barnes (1998), Ava Barnes (2000), Robert Barnes (1985), David Gray (1991), Joanne Miller (1994), Emily Young (2002)] */
這種方法用到了runtime的兩個特性,第一是keypath,也就是屬性鏈表,第二是kvc,也就是在運行時根據(jù)key查找對應(yīng)的值,并且selector也是運行時向給定的方法發(fā)送消息,然后對比兩個值.
用Swift來實現(xiàn)單一規(guī)則的排序是很簡單的
let p1 = people.sorted{ $0.frst.localizedStandardCompare($1.frst) == .orderedAscending}
但是如果要實現(xiàn)組合規(guī)則就有點麻煩了,標準庫有一個lexicographicallyPrecedes方法,能夠接受兩個序列,依次從兩個序列各取一個元素進行比較,如果第一個元素對比結(jié)果是相等,那就再用第二個元素,但是這個方法只能接收一個compare方法,比較名字就不能比較年齡
people.sorted { p0, p1 in
let left = [p0.last, p0.frst]
let right = [p1.last, p1.frst]
return left.lexicographicallyPrecedes(right) { $0.localizedStandardCompare($1) == .orderedAscending }
}
/*[Ava Barnes (2000), Ava Barnes (1998), Robert Barnes (1985), David Gray (1991), Joanne Miller (1994), Emily Young (2002)] */
foundation的排序描述符的方式要清晰不少旦委,但是它用到了運行時編程奇徒。而我們寫的Swift函數(shù)沒有使用運行時編程,不過它們不太容易寫出來或者讀懂缨硝。
排序描述符其實是一種描述對象順序的方式摩钙。我們現(xiàn)在不去將這些信息存儲在類里,而是嘗試定義一個函數(shù)來描述對象的順序查辩。最簡單的定義是獲取兩個對象胖笛,當它們的順序正確的時候返回 true。這個函數(shù)的類型正是標準庫中 sort(by:) 和 sorted(by:) 所接受的參數(shù)的類型宜岛。我們通過定義一個泛型的 typealias 來表述排序描述符.
也就是現(xiàn)在自己定義一個排序描述符:
/// ?個排序斷?长踊,當且僅當?shù)?個值應(yīng)當排序在第?個值之前時,返回 `true`
typealias SortDescriptor<Value> = (Value, Value) -> Bool
上面我們定義了一個閉包,它接受兩個參數(shù),返回一個bool值,現(xiàn)在來實現(xiàn)它:
let sortByYear: SortDescriptor<Person> = { $0.yearOfBirth < $1.yearOfBirth }
let sortByLastName: SortDescriptor<Person> = { $0.last.localizedStandardCompare($1.last) == .orderedAscending }
sortByYear和sortByLastName都是SortDescriptor類型,接下來實現(xiàn)真正的NSSortDescriptor,
我們可以定義一個函數(shù)萍倡,它和 NSSortDescriptor 大體相似身弊,但不涉及運行時編程的接口。這個函數(shù)接受一個鍵和一個比較函數(shù)列敲,返回排序描述符 (這里的描述符將是函數(shù)阱佛,而非 NSSortDescriptor)。在這里戴而,key 將不再是一個字符串凑术,而它自身就是一個函數(shù);給定正在排序的數(shù)組中的一個元素所意,它就會返回這個排序描述符所處理的屬性的值淮逊。然后,我們使用 areInIncreasingOrder 函數(shù)來比較兩個鍵扶踊。最后泄鹏,返回的結(jié)果也是一個函數(shù),只不過它被typealias 稍微包裝了一下:
func sortDescriptor<Value, Key>( key: @escaping (Value) -> Key, by areInIncreasingOrder: @escaping (Key, Key) -> Bool) -> SortDescriptor<Value> {
return { areInIncreasingOrder(key($0), key($1)) }
}
首先來描述一個這個方法,有點繞,
sortDescriptor定義兩個泛型Value和Key
最后返回SortDescriptor<Value>,因此Value將會是需要排序的對象,在這里是Person;
key接收一個Value返回一個Key;
return的是SortDescriptor,也就是說 { areInIncreasingOrder(key(1)) }這里的key(
1)是數(shù)值,那么Key就是Value的某個鍵的值.
接下來使用一下
let s : SortDescriptor<Person> = sortDescriptor { pp in
pp.yearOfBirth
} by: { k1, k2 in
k1 < k2
}
let s1 = people.sorted(by: s)
簡化一下:
let sortByYearAlt: SortDescriptor<Person> = sortDescriptor(key: { $0.yearOfBirth }, by: <)
people.sorted(by: sortByYearAlt)
/*[Robert Barnes (1985), David Gray (1991), Joanne Miller (1994), Ava Barnes (1998), Ava Barnes (2000), Emily Young (2002)] */
如果我們指定Key遵循Compare協(xié)議,就不需要第二個參數(shù)了
func sortDescriptor<Value, Key>(key: @escaping (Value) -> Key) -> SortDescriptor<Value> where Key: Comparable {
return { key($0) < key($1) }
}
let sortByYearAlt2: SortDescriptor<Person> = sortDescriptor(key: { $0.yearOfBirth })
到目前為止,sortDescriptor返回的都是與布爾值相關(guān)的函數(shù),而Foundation 中像是 localizedStandardCompare 這的 API秧耗,所返回的是一個包含 (升序命满,降序,相等) 三種值的 ComparisonResult 的結(jié)果,因此我們繼續(xù)改造sortDescriptor
func sortDescriptor<Value, Key>(key: @escaping (Value) -> Key, ascending: Bool = true, by comparator: @escaping (Key, Key) -> ComparisonResult) -> SortDescriptor<Value> {
return { lhs, rhs in
let order: ComparisonResult = ascending ? .orderedAscending : .orderedDescending
return comparator(key(lhs), key(rhs)) == order
}
}
let sortByYearAlt3: SortDescriptor<Person> = sortDescriptor(key: {$0.frst}, ascending: true) { $0.localizedStandardCompare($1) }
let p3 = people.sorted(by: sortByYearAlt3)
SortDescriptor 現(xiàn)在與 NSSortDescriptor 擁有了同樣地表達能力绣版,不過它是類型安全的胶台,而且不依賴于運行時編程歼疮。
我們現(xiàn)在只能用一個單一的 SortDescriptor 函數(shù)對數(shù)組進行排序。如果你還記得诈唬,我們曾經(jīng)用過NSArray.sortedArray(using:) 方法來用多個比較運算符對數(shù)組進行排序韩脏。我們可以很容易地為 Array,甚至是 Sequence 協(xié)議添加一個相似的方法铸磅。不過赡矢,我們需要添加兩次:一次是可變版本,另一次是不可變版本阅仔。
使用稍微不同的策略吹散,就可以避免添加多次擴展。我們可以創(chuàng)建一個函數(shù)來將多個排序描述符合并為單個的排序描述符八酒。它的工作方式和 sortedArray(using:) 類似:首先它會使用第一個描述符空民,并檢查比較的結(jié)果。然后羞迷,當結(jié)果是相等的話界轩,再使用第二個,第三個描述符衔瓮,直到全部用完:
func combine<Value>(sortDescriptors: [SortDescriptor<Value>]) -> SortDescriptor<Value> {
return { lhs, rhs in
for areInIncreasingOrder in sortDescriptors {
if areInIncreasingOrder(lhs, rhs) {
return true
}
if areInIncreasingOrder(rhs, lhs) {
return false
}
}
return false
}
}
現(xiàn)在把一開始的例子的foundation寫法換成SortDescriptor
let sortByLastName: SortDescriptor<Person> = sortDescriptor(key: {$0.last}, ascending: true) { $0.localizedStandardCompare($1) }
let sortByFirstName: SortDescriptor<Person> = sortDescriptor(key: {$0.frst}, ascending: true) { $0.localizedStandardCompare($1) }
let sortByYear: SortDescriptor<Person> = sortDescriptor(key: { $0.yearOfBirth }, by: <)
let combined: SortDescriptor<Person> = combine(sortDescriptors: [sortByLastName, sortByFirstName, sortByYear])
let p4 = people.sorted(by: combined)
最終浊猾,我們得到了一個與 Foundation 中的版本在行為和功能上等價的實現(xiàn)方法,但是我們的方式要更安全热鞍,也更符合 Swift 的語言習慣葫慎。因為 Swift 的版本不依賴于運行時,所以編譯器有機會對它進行更好的優(yōu)化薇宠。另外偷办,我們也可以使用它來對結(jié)構(gòu)體或是非 Objective-C 的對象進行排序。