swift 源码解析 -- Sequence

591 阅读3分钟

swift 源码解析 -- Sequence

常用功能分析

Sequence作为swift集合类协议扩展方法,为集合提供了一系列的序列迭代能力。最为常用的for-in,快速枚举遍历,都是通过此协议提供了最基础的迭代能力。

let animals = ["monkey", "tigger", "bird", "horse", "zebra"]
for animal in animals {
    print("animal : (animal)")
}
// animal : monkey
// animal : tigger
// animal : bird
// animal : horse
// animal : zebra

不仅如此,swift集合中的众多高阶函数,例如:map,fliter,reduce,flatMap等也都是该协议提供的方法,还有,contains,min,max等等,也都是其提供的方法。可以说,尽管代码不多,但是能力却相当强大。

let animals = ["monkey", "tigger", "bird", "horse", "zebra"]
print("animal : (animals.min())")
// animal : Optional("bird")

对于min的使用当然不止这么简单,还可以采用传入条件的形式:

let animals = ["monkey", "tigger", "bird", "horse", "zebra"]
print("animal : (animals.min(by: >))")
// animal : Optional("zebra")

传入大于号的方式显然是错的,但是通过这样的实验,也证明了其函数式的可构造性十分强大。那么其源码是如何实现的呢?

实现分析

迭代方法实现展示:

public protocol Sequence {
  // 类型占位符
  // 可在协议实现后确定协议类型
  associatedtype Element
​
  // 这是一个重要的东西
  // 迭代器的实现核心
  associatedtype Iterator: IteratorProtocol where Iterator.Element == Element
 }
public protocol IteratorProtocol {
  // 用于查找下一个对象
  // 如果找不到的话就返回nil
  mutating func next() -> Element?
}
​
// 来自于/collection目录
// collection协议的延展中实现IteratorProtocol事例
extension IndexingIterator: IteratorProtocol, Sequence {
  public typealias Element = Elements.Element
  public typealias Iterator = IndexingIterator<Elements>
  public typealias SubSequence = AnySequence<Element>
  // 从实现可以看出
  // 容器协议通过元素位置对于迭代器进行next返回
  @inlinable
  @inline(__always)
  public mutating func next() -> Elements.Element? {
    if _position == _elements.endIndex { return nil }
    let element = _elements[_position]
    _elements.formIndex(after: &_position)
    return element
  }
}
extension Collection where Iterator == IndexingIterator<Self> {
  // 生产迭代器,并返回
  @inlinable
  @inline(__always)
  public __consuming func makeIterator() -> IndexingIterator<Self> {
    return IndexingIterator(_elements: self)
  }
}

通过上面源代码可以知道每个序列协议内部,都存在一个迭代器协议,迭代器的内部接口其实十分简单,只是规定了next协议方法,但是这也很符合接口单一原则。通过协议不同实现,从而达到不同算法复杂度的结果。

在滚动占位符的设计中,利用了自定义迭代器的方法,从而使外部传入的数据集合到达可循环,可洗牌,可重新获取等目的。

public struct YouaInfiniteIterator<Iterator: IteratorProtocol>: IteratorProtocol {
    // 生成两个迭代器
    // 一个负责存储数据
    // 另一个负责当前迭代使用
    private let sampleIterator: Iterator
    private var currentIterator: Iterator
    // 是否可重复
    public var isRepeats: Bool = true
    public init(_ sample: Iterator) {
        self.sampleIterator = sample
        self.currentIterator = sampleIterator
    }
    
    public mutating func next() -> Iterator.Element? {
        // 如果当前迭代对象不为空
        // 则继续执行
        if let next = currentIterator.next() {
            return next
        } else if isRepeats == false {
            return nil
        }  else {
            // 迭代末端重新注入数据 
            self.currentIterator = sampleIterator
            return currentIterator.next()
        }
    }
}
​
public extension IteratorProtocol {
    func infinite() -> YouaInfiniteIterator<Self> {
        return YouaInfiniteIterator(self)
    }
}

这样可以在不修改数据结构的情况下,实现可重复容器。

在sequence的源码中,也扩展了例如DropFirstSequence,PrefixSequence等,对于迭代器进行了单独的处理封装,可以阅读代码了解。

除了对于迭代器的描述,高阶函数,也是swift中极为重要的特性,也是函数式编程中十分重要的实现手段,以高阶函数中最为基础的map函数为例:

extension Sequence {
@inlinable
  public func map<T>(
    _ transform: (Element) throws -> T
  ) rethrows -> [T] {
    // Collection.underestimatedCout 容器预估数量
    // 初始化空间
    let initialCapacity = underestimatedCount
    var result = ContiguousArray<T>()
    result.reserveCapacity(initialCapacity)
​
    var iterator = self.makeIterator()
​
    // 先加入预估的一波
    for _ in 0..<initialCapacity {
      result.append(try transform(iterator.next()!))
    }
    // 再将剩下的元素加进去
    while let element = iterator.next() {
      result.append(try transform(element))
    }
    return Array(result)
  }
}

尽管swift中高阶函数的实现方式,并没有采用递归的方式,但是,这里也符合柯里化思想。大体上,swift中迭代器实现的高阶函数,都是通过for-in与block结合使用,支持泛型,也可以扩展协议。

此外,还有一个方法需要注意一下。

  @inlinable
  public func contains(_ element: Element) -> Bool {
    if let result = _customContainsEquatableElement(element) {
      return result
    } else {
      return self.contains { $0 == element }
    }
  }

在默认条件下,contains方法是通过泛型中"=="方法来确定是否存在包含关系,可以通过运算符重载来达到多种对比的逻辑要求,例如,正常判断两个对象的引用地址(如果是值类型,则判断值),但如果这样的话,可能会导致判断失败,如果对象中包含属性id,则可以重载运算符,对比两个对象的id。

也可以通过增加协议实现_customContainsEquatableElement方法,达成以上目的。

参考文献

SwiftDoc.org

github apple/swift