Swift 4.1 遷移小技巧 —— CompactMap

四娘發表於2018-03-31

Swift 4.1 中引入了一個“新”函式 compactMap,在這裡給大家介紹一個遷移的小技巧。

compactMap 的由來

在開始之前,先簡單介紹一下 compactMap 的由來,我們都知道之前 flatMap 有兩個過載版本,第一個是用來 flat 集合的:

let arr = [[1, 2, 3], [4, 5]]

let newArr = arr.flatMap { $0 }
// newArr 的值為 [1, 2, 3, 4, 5]
複製程式碼

第二個是用來 flat 可選值的:

let arr = [1, 2, 3, nil, nil, 4, 5]

let newArr = arr.flatMap { $0 }
// newArr 的值為 [1, 2, 3, 4, 5]
複製程式碼

這兩個版本雖然都是用來降維的,但第二個版本除了 flat 之外其實還有 filter 的作用,在使用時容易產生歧義,所以社群認為最好把第二個版本重新拆分出來,使用一個新的方法命名,就產生了這個提案 SE-0187

最初這個提案用了 filterMap 這個名字,但後來經過討論,就決定參考了 Ruby 的 Array::compact 方法,使用 compactMap 這個名字。

遷移小技巧

雖然就 API 層面來說,這只是單純的名字修改,但全域性替換很難實現,而逐個編譯警告處理又太過麻煩,在這裡我想介紹一個更加便捷的遷移方法:構造一個 flatMap 來過載標準庫的實現,然後再借助 Xcode 的重構工具對函式進行重新命名。

方式很直白,唯一的問題在於如何過載,首先看一下 flatMap 的宣告,flatMap 是宣告在 Sequence 型別裡的:

// stdlib/public/core/SequenceAlgorithms.swift
extension Sequence {

  @inline(__always)
  @available(swift, deprecated: 4.1, renamed: "compactMap(_:)",
    message: "Please use compactMap(_:) for the case where closure returns an optional value")
  public func flatMap<ElementOfResult>(
    _ transform: (Element) throws -> ElementOfResult?
  ) rethrows -> [ElementOfResult] {
    return try _compactMap(transform)
  }
}
複製程式碼

再參考我之前的博文【譯】Swift 泛型宣言

...

參考 Swift 官方文件 Protocols 小節裡的最後一段:

“If a conforming type satisfies the requirements for multiple constrained extensions that provide implementations for the same method or property, Swift will use the implementation corresponding to the most specialized constraints.”

約束越多的 conformance 優先順序越高

...

綜上可得,由於 flatMap 是一個宣告在 Sequence 裡的泛型函式,所以我們可以在一個約束更多的 extension 裡宣告一個 flatMap 進行過載,例如繼承自 SequenceCollection

protocol Collection: Sequence {}
複製程式碼

又或者是某個 Nested Type,例如 Array,鑑於我們專案裡基本上都是使用 Array.flatMap,所以直接過載即可:

extension Array {

  func flatMap<ElementOfResult>(
    _ transform: (Element) throws -> ElementOfResult?
  ) rethrows -> [ElementOfResult] {
    return try compactMap(transform)
  }
}
複製程式碼

接著右鍵 -> Refactor -> Rename,把 flatMap 改成 compactMap

Swift 4.1 遷移小技巧 —— CompactMap

最後把我們剛剛過載的方法刪掉,遷移完成了!!!

覺得文章還不錯的話可以關注一下我的部落格

相關文章