首先在腦海里回想一下 map 和 flattenMap 的區(qū)別。flattenMap 會將 transform 函數(shù)的返回類型先拍扁,再組合成本身的復(fù)合類型。標準庫有3個 flatMap。
Sequence.flatMap<S>(_: (Element) -> S)
-> [S.Element] where S : Sequence
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
在 Swift 4.1 中引入的 compactMap 的新函數(shù)實際上是第三個版本的重命名,沒什么其它特別之處。想知道 Why 的同學(xué)們,請繼續(xù)看下去,我將帶著大家繼續(xù)探究一下這么修改的理由。在這之前,我們首先復(fù)習一下 map 和 flatMap 的各個版本。
1.最廣為人知的 Sequence.flatMap
Sequence.flatMap<S>(_: (Element) -> S)
-> [S.Element] where S : Sequence
let numbers = [1, 2, 3, 4]
let mapped = numbers.map {
Array(repeating: $0, count: $0)}
// [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]
let flatMapped = numbers.flatMap {
Array(repeating: $0, count: $0) }
// [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
上述代碼意圖是:數(shù)字是幾就重復(fù)幾次,但 map 與 flatMap 的結(jié)果有明顯區(qū)別。
map 是沒拍扁的:closure 返回的是數(shù)組,map 最終將它們組成一個二維的數(shù)組;
flatMap 中執(zhí)行的 closure 返回的是同樣的數(shù)組,但是 flatMap 將每一個返回的數(shù)組都拍扁,取出它的元素,構(gòu)成一個大的一維數(shù)組。
這個版本的 map 和 flatMap 是大家最為熟悉的。這里的s.flatMap(transform)等價于Array(s.map(transform).joined())
2.Optional也有flatMap
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Optional的版本知道的和日常使用的就沒有那么多了。先仔細看一下這兩個函數(shù)的定義,區(qū)別僅在一個處:
func map<U>( transform: (Wrapped) -> U) -> U?
func flatMap<U>( transform: (Wrapped) -> U?) -> U?
我們首先從概念上理解這兩個函數(shù)的區(qū)別:
map 函數(shù)是:在 transform 函數(shù)返回的類型上,最終給你再套一個 Optional ;
flatMap 函數(shù)從概念上來講則是在 transform 函數(shù)返回結(jié)果后,幫你退一層 Optional,再幫你套一個。而對于 Optional 這個特別的例子,由于 transform 的返回類型恰好等同于整個函數(shù)的返回類型,都是 U?,所以函數(shù)內(nèi)部實現(xiàn)可以直接返回 transform 的運行結(jié)果,而不用做解開一個再套上一個 Optional 的多余動作了。
我們再從用途上看看 flatMap 的作用。如果 Optional 不是使用 flatMap,那么在一個 Optional上,執(zhí)行一個返回任意類型 Optional 的函數(shù),那么可能得到的類型是兩層 Optional。而 flatMap幫助我們壓掉了一個層,所以叫 flatMap。以下是 Optional 的 map 和 flatMap 的用法:
let possibleNumber: Int? = Int("42")
let possibleSquare = possibleNumber.map { $0 * $0 }
let possibleURL: URL? = URL(string: "https://www.liulishuo.com")
let possibleHost = possibleURL.flatMap{$0.host}
從上面的例子中可以看到,Optional 版本的 map 和 flatMap,差別還是在于 closure 中的返回值,前者乘法表達式是非 Optional 的,后者 $0.host 是 Optional 的,兩個段代碼分別都得到了自己所預(yù)期的一層Optional的結(jié)果。
了解了這個兩個函數(shù)的用法后,我們不免聯(lián)想到,是否可以換用 Optional Chaining 實現(xiàn)同樣的功能呢?,我嘗試寫了看下面兩段“等價的”代碼:
// 編譯不過
let possibleSquare2 = Int("42")?.map{ $0 * $0 }
// 可以編譯
let possibleHost2 = possibleURL?.host
第一個例子,看上去很合理,似乎是在一個 Optional 上調(diào)用 map 方法?實則編譯不過,因為Int("42")是個 Optional ,后面的問號是 Optional Chaining 的語法,Int 上并沒有 map 方法,編譯不過。這里提醒我們 Optional 的 Chaining 和 Mapping 即便可以混用也謹慎使用,表達式有一定的復(fù)雜度的話可能更會看混意義。
在第二個例子中,使用 Optional Chaining 是個更簡單的寫法。
那么問題來了,什么時候使用 Optional Chaining,什么時候使用 Optional 的 Mapping 呢?
我認為,他們的差異在于 receiver 在類型轉(zhuǎn)換過程中承擔的角色,Optional Chaining 中只能作為 receiver,而 Optional Mapping 函數(shù)還能作為參數(shù),更為通用,因而第一個例子只能用 Mapping 實現(xiàn);另一個方面,Optional Chaining 只能寫成串聯(lián)表達式,而 closure 當中可以寫更復(fù)雜的轉(zhuǎn)換邏輯,還是用例子來說明:
let possibleNumber: Int? = Int("42")
let nonOverflowingSquare = possibleNumber.flatMap {
x -> Int? in
let (result, overflowed) = x.multipliedReportingOverflow(by: x)
return overflowed ? nil : result
}
3. 另一個Sequence.flatMap被改名
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
終于聊到了今天的主角,這個函數(shù)壓扁 Optional 類型,組成數(shù)組,看上去很綜合。我們通過一個例子先了解一下它的設(shè)計初衷。
let possibleNumbers = ["1", "2", "three", "http:///4///", "5"]
let flatMapped: [Int] = possibleNumbers.flatMap {
str in Int(str) }
// [1, 2, 5]
很有用有沒有,完美的拯救了一個 failable 的 mapping,如果這里使用 map,就返回 [Int?] 了而且包含 nil。因此這個函數(shù)很有用的,但我們看一下這個例子:
struct Person {
var age: Int
var name: String
}
func getAges(people: [Person]) -> [Int] {
return people.flatMap { $0.age }
}
這可以編譯嗎?是的,因為 Int 可以隱式轉(zhuǎn)成 Int?,再參與壓扁。但其實這一來二去沒有一點必要,況且,為什么不用 map?是的,這里應(yīng)該用 map。所以,為了避免這樣的誤用,在Swift 4.1 中會提示:
'flatMap' is deprecated: Please use compactMap(_:) for the case where closure returns an optional value
當closure返回Optional的時候,在Swift 4.1 中應(yīng)該使用compactMap。
let flatMapped: [Int] = possibleNumbers.compactMap {
str in Int(str) }
這樣一來,一些針對map和flatMap重載來設(shè)計的燒腦面試題也可以退散了。
4. 小結(jié)
- 回顧了 flatMap 和 map 的區(qū)別在于前者壓扁后者沒有。
- 比較了 Optional Mapping 和 Chaining 簡單用法上的區(qū)別。
- 解釋了為什么另一個版本的
Sequence.flatMap需要改名成compactMap。

Swift 4.1 新特性系列文章
Swift 4.1 新特性 (1) Conditional Conformance
Swift 4.1 新特性 (2) Sequence.compactMap
Swift 4.1 新特性 (3) 合成 Equatable 和 Hashable
Swift 4.1 新特性 (4) Codable的改進