Swift源码阅读 - Lazy机制的实现方法

上一节,我们讨论了Sequence中一些eager API的问题。而解决这个问题的办法,就是把原始的Sequence和要执行的动作保存起来,等我们真正需要其中的数据时,再“按需”调用对应的动作。顺着这个思路,我们来看Swift是如何实现的。

_SquenceWrapper

为了能把原始的Sequence“包”起来,Swift中定义了一个protocol _SequenceWrapper,它的定义在这里:

@_show_in_interface
public // @testable
protocol _SequenceWrapper : Sequence {
  associatedtype Base : Sequence where Base.Element == Element
  associatedtype Iterator = Base.Iterator
  associatedtype SubSequence = Base.SubSequence

  var _base: Base { get }
}

这里,@_show_in_interface用于把_SequenceWrapper约束的接口在“某些情况”下显示在编译器生成的头文件里,大家可以在SR-984中找到一些相关的讨论,不过从阅读源代码的角度,我们还不用太在意这些细节。

接下来,在_SequenceWrapper的定义中:

  • Base:表示_SequenceWrapper“包装”的原始序列类型,可以看到,这里我们要求Base.Element_SequenceWrapper.Element是相同的;
  • IteratorSubSequence则是Sequence类型约束,显然,它们应该和Base中对应的类型是相同的;
  • 最后,_base用于返回被_SequenceWrapper“包装”的原始序列;

接下来,_SequenceWrapper通过extension为一些Sequence中的方法提供了默认实现,当然,就是把API转接到“包装过”的base

extension _SequenceWrapper {
  @inlinable // FIXME(sil-serialize-all)
  public func map(
    _ transform: (Element) throws -> T
  ) rethrows -> [T] {
    return try _base.map(transform)
  }
}

这样,我们只要定义一个遵从_SequenceWrapper的类型,就有机会“缓存”对原始序列的各种操作了。

LazySequence

在上一节末尾,我们提到了Sequence中的lazy属性:

extension Sequence {
  @inlinable // FIXME(sil-serialize-all)
  public var lazy: LazySequence {
    return LazySequence(_base: self)
  }
}

接下来,我们就来看下这个LazySequence的定义:

@_fixed_layout // FIXME(sil-serialize-all)
public struct LazySequence: _SequenceWrapper {
  public var _base: Base

  @inlinable // FIXME(sil-serialize-all)
  internal init(_base: Base) {
    self._base = _base
  }
}

可以看到,没什么有营养的东西。LazySequence保存了一份原始序列的拷贝,并且,通过遵从_SequenceWrapperLazySequence就有机会接管原本应该调用的_base中的方法。

接下来,LazySequence还有一个extension

extension LazySequence: LazySequenceProtocol {
  public typealias Elements = Base

  /// The `Base` (presumably non-lazy) sequence from which `self` was created.
  @inlinable // FIXME(sil-serialize-all)
  public var elements: Elements { return _base }
}

它暴露了两个信息:一个是Elements类型的属性elements;一个是LazySequenceProtocol。为什么要在这个extension中定义一个和Base一样的类型呢?这个element属性又是做什么的呢?LazySequenceProtocol又约束了什么呢?

LazySequenceProtocol

直接来看它的定义:

public protocol LazySequenceProtocol : Sequence {
  associatedtype Elements : Sequence = Self
    where Elements.Iterator.Element == Iterator.Element

  var elements: Elements { get }
}

extension LazySequenceProtocol {
  @inlinable // FIXME(sil-serialize-all)
  public var lazy: LazySequence {
    return elements.lazy
  }
}

extension LazySequenceProtocol
  where Elements: LazySequenceProtocol {
  @inlinable // FIXME(sil-serialize-all)
  public var lazy: Elements {
    return elements
  }
}

extension LazySequenceProtocol where Elements == Self {
  /// Identical to `self`.
  @inlinable // FIXME(sil-serialize-all)
  public var elements: Self { return self }
}

可以看到,实际上LazySequenceProtocol只定义了两个约束,分别是:elementslazy。其中,elements是原始的序列,而lazy则表示elements被“延迟处理”之后的序列。正是这两个属性,约定了我们访问“普通版”和“延迟版”序列的方法。这样,我们也就不难理解LazySequence的定义了。

另外,LazySequenceProtocol还为一些特殊情况定义了elementslazy的默认实现:

  • 当要封装的序列自身就遵从LazySequenceProtocol时,它的lazy属性直接返回封装的序列自身就好了,就没必要再用LazeSequence封装一层了;
  • 当要封装的序列和遵从LazySequenceProtocol的类型相同时,elements当然应该就是它自己;

添加延迟处理的行为

看到这,你可能会想,这也没延迟处理什么啊。的确,到目前为止,我们只定义了一个具备接口转发能力的LazySequence以及约束LazySequence访问方式的接口LazySequenceProtocol。因此,我们刚完成在这一节开始提到的前半部分功能:把原始的Sequence保存起来。接下来,我们就得对着Sequence支持的方法,分别实现每一个要“延迟处理”的方法了。这里,我们用map来举例。

在Map.swift里,我们可以找到一个extension LazySequenceProtocol

extension LazySequenceProtocol {
  /// Returns a `LazyMapSequence` over this `Sequence`.  The elements of
  /// the result are computed lazily, each time they are read, by
  /// calling `transform` function on a base element.
  @inlinable
  public func map(
    _ transform: @escaping (Elements.Element) -> U
  ) -> LazyMapSequence {
    return LazyMapSequence(_base: self.elements, transform: transform)
  }
}

因此,当我们调用一个遵从了LazySequenceProtocol对象的map方法,会得到一个LazyMapSequence对象。它的定义在这里:

@_fixed_layout
public struct LazyMapSequence {

  public typealias Elements = LazyMapSequence

  @usableFromInline
  internal var _base: Base
  @usableFromInline
  internal let _transform: (Base.Element) -> Element

  /// Creates an instance with elements `transform(x)` for each element
  /// `x` of base.
  @inlinable
  internal init(_base: Base, transform: @escaping (Base.Element) -> Element) {
    self._base = _base
    self._transform = transform
  }
}

在它的定义里:

  • _base表示封装的原始序列;
  • Element是原始序列中的元素变换之后的类型;
  • _transform是暂存的用于变换的方法;

于是,还是上一节提到的Fibonacci序列,当我们使用:

let fibo = Fibonacci()
var mcount = 0

let lazyFibo = fibo.lazy.map {
    (n: Int) -> Int in
    mcount += 1
    print("\(mcount) map")
    return n*2
}

的时候,就不会立即map所有的元素了,而只是会把原始的fibo和要变换的closure都暂时缓存起来。那什么时候才会真正执行变换呢?答案当然是真正访问序列中元素的时候,例如:

var iter = lazyFibo.makeIterator()
for _ in 1...5 { iter.next() }

执行一下,就会在控制台看到这样的结果:

1 map
2 map
3 map
4 map
5 map

也就是说,实际的变换,只发生了5次,而不是eager版本的1000次,而这,就是lazy属性为Fibonacci带来的效果。但是,为什么访问LazyMapSequence就可以按需获取了呢?答案当然在与它搭配工作的Iterator里。

LazyMapSequence.Iterator

它的定义在这里:

extension LazyMapSequence {
  @_fixed_layout
  public struct Iterator {
    @usableFromInline
    internal var _base: Base.Iterator
    @usableFromInline
    internal let _transform: (Base.Element) -> Element

    @inlinable
    public var base: Base.Iterator { return _base }

    @inlinable
    internal init(
      _base: Base.Iterator,
      _transform: @escaping (Base.Element) -> Element
    ) {
      self._base = _base
      self._transform = _transform
    }
  }
}

可以看到,都是一些模板代码,没什么实际的东西。而真正的秘密,在这个Iteratornext方法里:

extension LazyMapSequence.Iterator: IteratorProtocol, Sequence {
  @inlinable
  public mutating func next() -> Element? {
    return _base.next().map(_transform)
  }
}

可以看到,“延迟版本”的map,会先从原始序列中取得下一个元素(_base.next()),然后单独对它进行变换。这样,就实现了“按需变换”的效果。

最后,只要让LazyMapSequence遵从LazySequenceProtocol,并定制这种况下的makeIterator方法就好了:

extension LazyMapSequence: LazySequenceProtocol {
  @inlinable
  public func makeIterator() -> Iterator {
    return Iterator(_base: _base.makeIterator(), _transform: _transform)
  }
}

LazyMapSequence遵从了LazySequenceProtocol之后,它就有了一个lazy属性,由于这个属性也是遵从LazySequenceProtocol的,因此,当我们使用lazy.map的时候,就会得到LazyMapSequence对象,当我们调用这个对象的makeIterator,就会是上面定义的这个方法。而这,就是“延迟变换”所有的秘密了。

你可能感兴趣的:(Swift源码阅读 - Lazy机制的实现方法)