上一节,我们讨论了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
是相同的; - 而
Iterator
和SubSequence
则是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
保存了一份原始序列的拷贝,并且,通过遵从_SequenceWrapper
,LazySequence
就有机会接管原本应该调用的_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
只定义了两个约束,分别是:elements
和lazy
。其中,elements
是原始的序列,而lazy
则表示elements
被“延迟处理”之后的序列。正是这两个属性,约定了我们访问“普通版”和“延迟版”序列的方法。这样,我们也就不难理解LazySequence
的定义了。
另外,LazySequenceProtocol
还为一些特殊情况定义了elements
和lazy
的默认实现:
- 当要封装的序列自身就遵从
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
}
}
}
可以看到,都是一些模板代码,没什么实际的东西。而真正的秘密,在这个Iterator
的next
方法里:
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
,就会是上面定义的这个方法。而这,就是“延迟变换”所有的秘密了。