Swift 4.1 迁移小技巧 —— CompactMap

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

Screen Shot 2018-04-01 at 01.13.41

最后把我们刚刚重载的方法删掉,迁移完成了!!!