美文网首页
Swift源码阅读 - Lazy机制的实现方法

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

作者: 醉看红尘这场梦 | 来源:发表于2020-03-16 17:10 被阅读0次

    上一节,我们讨论了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<T>(
        _ 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<Self> {
        return LazySequence(_base: self)
      }
    }
    
    

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

    @_fixed_layout // FIXME(sil-serialize-all)
    public struct LazySequence<Base : Sequence>: _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<Elements> {
        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<U>(
        _ transform: @escaping (Elements.Element) -> U
      ) -> LazyMapSequence<Self.Elements, U> {
        return LazyMapSequence(_base: self.elements, transform: transform)
      }
    }
    
    

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

    @_fixed_layout
    public struct LazyMapSequence<Base : Sequence, Element> {
    
      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机制的实现方法

          本文链接:https://www.haomeiwen.com/subject/mujyjhtx.html