今天简单说一下,Swift中Array的常用一组函数—— map 系函数。

大家有时可能会对 map\compactMap\flatMap 这三个函数的使用场景傻傻分不清,希望通过这篇文章,能够正确的理解与灵活运用。

进而对集合类型的这类函数都可以举一反三。

map函数,我个人感觉是这这三个中最基础和最简单的函数。

它的作用就是 对数组的每个元素做转换,形成一个新数组。

@inlinable public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]

大家注意的是,它的返回是一个泛型,而通过Swift类型的自动推断,有的时候会不知道返回的到底是什么类型,需要仔细阅读map里面这个转换。

let cast = ["Vivien", "Marlon", "Kim", "Karl"]
let lowercaseNames = cast.map { $0.lowercased() }
print(lowercaseNames)
["vivien", "marlon", "kim", "karl"]
//////////////////////////////////////////////
let letterCounts = cast.map { $0.count }
print(letterCounts)
[6, 6, 3, 4]

其实map函数有很多简化写法,非常容易迷惑人,来花式秀一下:

[1, 2, 3].map({ (i: Int) -> Int in return i * 2 })
[1, 2, 3].map({ i in return i * 2 } )
[1, 2, 3].map({ i in i * 2 })
[1, 2, 3].map({ $0 * 2 })
[1, 2, 3].map() { $0 * 2 }
[1, 2, 3].map{ $0 * 2 }

是不是有这种感觉:

compactMap

compactMap可以认为是map进阶版本,在转换过程中过滤掉数组中的nil元素,让我们继续看个例子:

let possibleNumbers = ["1", "2", "three", "///4///", "5"]
let mapped: [Int?] = possibleNumbers.map { str in Int(str) }
[1, 2, nil, nil, 5]
//////////////////////////////////////////////
let compactMapped: [Int] = possibleNumbers.compactMap { str in Int(str) }
[1, 2, 5]

flatMap

相比较map和compactMap,flatMap具有一定的迷惑性。

作为去nil功能时,它过期了

比如你这么写函数,会得到一个告警:

看源代码,上面也说得非常的清晰,过期了:

@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]

这个时候编译器建议你使用compactMap函数

作为二维数组降成一维数组的API调用,才是flatMap的真正用法

当数组是一个二维数组的时候,使用flatMap函数它不会报告警说方法过期,而是会对数组进行展平操作——二向箔攻击:

let someArray = [[1, 2, 3], [4, 5, 6]].flatMap { $0 }
print(someArray)
[1, 2, 3, 4, 5, 6]

实际调用的是这个API:

@inlinable public func flatMap<SegmentOfResult>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence

是不是感觉有点蒙圈?一会说过期,一会又可以正常使用。

主要还是要看flatMap函数具体实现了什么功能。

大家想想,如果是下面这个数组,能够顺利变成一维数组么:

let anotherArray = [[1, 2, 3], [4, 5, [6, 7]]].flatMap { $0 }
print(anotherArray)

结果是[1, 2, 3, 4, 5, [6, 7]],没有完全展平。

flatMap它只能展平二维数组。如果一个二维数组中还有数组,它就无法展平了。

map函数的内部实现

其实map函数的内部实现并不复杂,其实主要就是用了for循环。

extension Array {
    func map<T>(_ transform: (Element) -> T) -> [T] {
        var newArray:[T] = []
        for element in self {
            let newElement = transform(element)
            newArray.append(newElement)
        return newArray

官方版说实话比我想的要复杂,而且是放在Collection协议的extension中,并且官方版添加了对错误的处理。

感兴趣的看看源码:Swift Collection

extension Collection {
  @inlinable
  public func map<T>(
    _ transform: (Element) throws -> T
  ) rethrows -> [T] {
    // TODO: swift-3-indexing-model - review the following
    let n = self.count
    if n == 0 {
      return []
    var result = ContiguousArray<T>()
    result.reserveCapacity(n)
    var i = self.startIndex
    for _ in 0..<n {
      result.append(try transform(self[i]))
      formIndex(after: &i)
    _expectEnd(of: self, is: i)
    return Array(result)

从Array到Collection

既然官方的map函数写在Collection中的extension里面,那么你可以想想其他集合类型的map系函数的使用是不是也大同小异呢?

之前我写的这篇文章,被很多掘友认同,我感到很惊讶:Swift:为String、Array、Dictionary添加isNotEmpty属性

我想了半天,得到了一个结论,是不是因为大家一般写Swift的extension的时候,一般都是在原生上的class或者struct去做操作,而很少在protocol这种没有具体实现的层面进行处理导致的呢?

Swift:为String、Array、Dictionary添加isNotEmpty属性

本篇针对Swift的Array讲解了map\compactMap\flatMap函数,大家找到合适的使用场景,就可以完成相对比较复杂的功能。

需要注意的是flatMap函数,它作为去数组中nil元素的功能已经过期,请使用compactMap,它作为展平函数,只能展平二维数组。

请活用高级函数去代替forin循环。

通过查看源码,从Array抽象到Collection协议,我们可以感受到Swift设计的思路。

我们下期见,应该没下期了,我已经更完32篇了,嘻嘻嘻。

  • 私信
     2,822