Swift進階七: 函數(shù)1

首先回顧基礎(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(0), key(1)) }這里的key(0)和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 的對象進行排序。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昼接,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子悴晰,更是在濱河造成了極大的恐慌慢睡,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铡溪,死亡現(xiàn)場離奇詭異漂辐,居然都是意外死亡,警方通過查閱死者的電腦和手機棕硫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門髓涯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人哈扮,你說我怎么就攤上這事纬纪◎驹伲” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵包各,是天一觀的道長摘仅。 經(jīng)常有香客問我,道長问畅,這世上最難降的妖魔是什么娃属? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮护姆,結(jié)果婚禮上矾端,老公的妹妹穿的比我還像新娘。我一直安慰自己卵皂,他們只是感情好秩铆,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著渐裂,像睡著了一般豺旬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上柒凉,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天族阅,我揣著相機與錄音,去河邊找鬼膝捞。 笑死坦刀,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蔬咬。 我是一名探鬼主播鲤遥,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼林艘!你這毒婦竟也來了盖奈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤狐援,失蹤者是張志新(化名)和其女友劉穎钢坦,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體啥酱,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡爹凹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了镶殷。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片禾酱。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出颤陶,到底是詐尸還是另有隱情颗管,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布指郁,位于F島的核電站忙上,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏闲坎。R本人自食惡果不足惜疫粥,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望腰懂。 院中可真熱鬧梗逮,春花似錦、人聲如沸绣溜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽怖喻。三九已至底哗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锚沸,已是汗流浹背跋选。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留哗蜈,地道東北人前标。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像距潘,于是被迫代替她去往敵國和親炼列。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Objective-C VSSwift的排序方式音比。 學習目的:如何將函數(shù)作為參數(shù)使用俭尖,并且將函數(shù)當作數(shù)據(jù),以完全類...
    SueStudy閱讀 274評論 0 0
  • 學習目的:Swift如何將函數(shù)作為參數(shù)使用洞翩,并且將函數(shù)當作數(shù)據(jù)稽犁,以完全類型安全的方式復(fù)制同樣的OC功能 例子:Ob...
    SueStudy閱讀 429評論 1 0
  • Swift中四種基本排序方法 不可變版本的 sorted(by:) 可變的 sort(by:) 兩者在待排序?qū)ο笞?..
    Seacen_Liu閱讀 1,122評論 0 1
  • Swift進階 - 個人總結(jié) 本章內(nèi)容來自于喵神翻譯的Swift進階,有興趣的同學可以閱讀原書菱农,更加詳細缭付! 本章內(nèi)...
    阿奈閱讀 1,626評論 0 7
  • Advanced-Swift-Sample-Code 6. 編碼和解碼 概覽 /// 某個類型可以將?身編碼為?種...
    錢噓噓閱讀 902評論 0 1