玩轉Swift高階函式

語言: CN / TW / HK

在我們做函式表示式的時候,有太多選擇去操作一個函式,在swift特性中我們將它稱之為高階函式。

在學習過Rxswift的人一定也不陌生,這些Rx操作符或者說swift高階函式,一定程度上簡化了函式轉換的內部過程,其特殊性和易用性也成為了開發日常使用的香餑餑,以下關於高階函式的內容,希望我的理解方向是對的。

起初學Swift語法的時候,我們能看見很多使用OC時不曾見過的函式,如sort、sorted

隨著語法朝著Swift5演變,使更好的方法得以保留,也變成了如今的這種常見樣式sorted、map、compcatMap、reduce

通過使用高階函式,對函式中的元素產生不同的對映結果,通過組合、排序、過濾、分離,最後得到了新的元素結果集合,為了儘早熟悉便於理解,我也將場景型別一一區分開。

場景一 排序

可變陣列升降序排列

var nums = [11, 3, 3, 4, 5, 6, 5, 1] nums.sort()//預設升序 nums.sort(by: >)//降序 ​ print nums: 升序 [1, 3, 3, 4, 5, 5, 6, 11] 降序 [11, 6, 5, 5, 4, 3, 3, 1]

不可變陣列升降序排列

let aar = [1, 3, 2, 4] let newArr = aar.sorted()//預設升序 let newArr = aar.sorted(by: >)//降序 ​ print arr: 升序和降序 [1, 3, 2, 4] ​ print newArr: 升序 [1, 2, 3, 4] 降序 [4, 3, 2, 1]

字典排序

let newDic = dic.sorted{ $0.0 > $1.0 } ​ print key: 降序 [(key: "word", value: 555), (key: "idt", value: 0), (key: "id", value: 123)] ​ print value: 降序 [(key: "word", value: 555), (key: "id", value: 123), (key: "idt", value: 0)]

一組資料模型的id升降序排列

struct GameInfo {   var name: String?   var id: Int?   var title: String?   var date: String? } var data =       [           GameInfo(name: "yyds", id: 0, title: "王者榮耀", date: "1632366361"),           GameInfo(name: "letgo", id: 2, title: "王者榮耀", date: "1632366395"),           GameInfo(name: "cc", id: 1, title: "火影忍者", date: "1632366995")       ]           let max = data.sorted { (mod0, mod1) -> Bool in           mod0.id! < mod1.id! } ​ print newData: [["date": 1632366361, "id": 0, "name": "yyds", "title": "王者榮耀"], ["id": 1, "title": "火影忍者", "date": 1632366995, "name": "cc"], ["id": 2, "title": "王者榮耀", "name": "letgo", "date": 1632366395]]

sort:可以進行簡單到複雜資料結構的排序。sort無返回,在原陣列上修改;sorted返回有序陣列,不會修改原陣列

場景二 查詢

統計某個區間的分值,並得到人數

let number = [10, 30, 50, 0 , 90, 91, 100, 98, 0] let excellent = number.filter{ $0 <= 100 && $0 >= 90 } let good = number.filter{ $0 == 100 } let failed = number.filter{ $0 < 60 } let zero = number.filter{ $0 == 0 }         let fraction = excellent.map { "($0)分" } let zero_fraction = failed.map { "($0)分" } ​ print: 分數90以上(包括90)有 ,4人; 分別是 ["90分", "91分", "100分", "98分"] 分數100有 ,1人 分數不及格有 ,5人; 分別是 ["10分", "30分", "50分", "0分", "0分"] 分數零分有 ,2人

找到navigationController棧中viewController,並改變對應title、backgroundColor

navigationController?.viewControllers.filter{ (vc) -> Bool in vc.title == "誒呀呀" }.map { $0.title = "鴿鴿" $0.view.backgroundColor = .green }

filter:用於過濾出陣列中滿足條件的元素,生成新的陣列;同時還能在過濾條件的基礎上對新陣列中的元素進行對映

場景三 計算和轉換

菜場買菜 張阿姨拿了五元和陳伯伯拿了二十元去買菜,他們都要買胡蘿蔔和黃瓜,並且需要留一塊錢坐車回家,請問張阿姨和陳伯伯各買了多少(胡蘿蔔1元,黃瓜2元)

let nums = ["張阿姨": 5, "陳伯伯": 20]         let money = nums.map { (person) -> String in let remaining = person.value - 1 let all = remaining / 3 var carrot = 0 var cucumber = 0 if remaining % 3 == 1 { carrot = 1 }else if remaining % 3 == 2 { cucumber = 2 } return "(person.key)買了 (all + carrot)個胡蘿蔔和(all + cucumber)個黃瓜"             } ​ print: ["陳伯伯買了 7個胡蘿蔔和6個黃瓜", "張阿姨買了 2個胡蘿蔔和1個黃瓜"]

解多層集合效果

let arr = [[1, 2, 3],[5, 5, 5],[3, 5, 7]] let newarr = arr.flatMap{ $0 } ​ print: [1, 2, 3, 5, 5, 5, 3, 5, 7]

建立多層效果

let arr = [1, 2, 3, 4] let abc = aar.compactMap { Array(repeating: $0, count: $0) } [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]

map:通過對集合中的元素對映轉化,返回一個泛型的陣列集合;compactMap會將轉換失敗為nil的元素過濾掉,其它的和map相同;flatMap會改原始集合的層級結構,用來合併元素,並可以看做

所以,swift4.1以後在閉包返回可選的情況下使用compactMap,一般情況下可以使用map。

更強大的場景:reduce

我曾經遇到過這樣一種情況,需要在evaluateJavaScript方法中拿到cookies並將它從最初不標準的String轉化成標準的jsonString格式,我是這麼做的

webView.evaluateJavaScript("document.cookie") { (cookie, error) in               let cook = cookie as? String ?? ""                               let array = cook.replacingOccurrences(of: " ", with: "").components(separatedBy: ";")                               let units = array.reduce(into: [String: String]()) { result, key in                   let arr = key.components(separatedBy: "=")                   let key = arr[0]                   var value: String?                   value = arr[1]                   result[key] = value ?? ""                                   }               guard let pt_cookies = units.toJSONString() else {                   return               }               print(pt_cookies)           }

可以說不需要複雜的操作,不需要對字元陣列複雜的提取,也不需要在字典中頻繁的操作,轉換過程就在內部輕易的實現了。正是使用了reduce,它滿足我們對高階函式的一切需求和嚮往。

reduce的兩個函式

@inlinable public func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result ​ @inlinable public func reduce<Result>(into initialResult: __owned Result, _ updateAccumulatingResult: (inout Result, Element) throws -> ()) rethrows -> Result

主要在於nextPartialResult: (Result, Element)updateAccumulatingResult: (inout Result, Element)

一個是將遍歷後呼叫閉包的結果賦值給臨時變數,一個是遍歷後,將變數的地址作為閉包的第一引數,而執行閉包就是在對變數值進行操作。

使用reduce實現map

let list = [1, 2, 3] let s = list.reduce(into: String()) { (red, key) in red.append("(key)") } ​ print: "123"

使用reduce實現filter

let nums = [11, 3, 3, 4, 5, 6, 5, 1] let reduce = nums.reduce(into: [Int]()) { (result, key) in if key > 4 { result.append(key) } } ​ print: [11, 5, 6, 5]

使用reduce鏈式組合實現sorted

let r = nums.sorted(by: >).reduce(into: [Int]()) { (result, key) in if key > 4 { result.append(key) } }

上面使用的reduce函式中,into表示初始型別(可以設定預設值),閉包元祖中的第一個引數是容器,第二個引數是原始序列逐一遍歷的元素。它能幫助我在有限的程式碼中有效的表達我的意圖,使得內部結構更直接。

高階函式對地址的影響

``` var arr = [1, 2, 3, 4] let b1 = withUnsafePointer(to: &arr) {$0} arr.map{ $0 } let b2 = withUnsafePointer(to: &arr) {$0} var filter = arr.filter{ $0 < 4 } let b3 = withUnsafePointer(to: &filter) {$0}

print: b1 0x00007ffee289c300 b2 0x00007ffee289c300 b3 0x00007ffee3a392c0 ```