上篇博客中,我们初步了解了为什么需要一个 Type Eraser ,
也分享了一个简单实现 Type Erasure 的方案。
为了更加深入了解类型擦除,我们还是得来看看 Swift 自带的一些 Type Eraser 是如何实现的。
目标是:理解一下其基本思路,并且仿制一个出来。
底层原理 为了方便理解,我们聚焦于一个 Type Eraser:AnyIterator
为什么是它呢?
因为它出现在源码 的最上面😂,而且下面的 AnySequence
和 AnyCollection
都得回到 AnyIterator
。
先看源码(我这里去除了注释和一些不太重要的代码,方便大家阅读):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 @inline (never)@usableFromInline internal func _abstract ( file : StaticString = #file , line : UInt = #line ) -> Never { fatalError ("Method must be overridden" , file: file, line: line) } public struct AnyIterator <Element > { @usableFromInline internal let _box: _AnyIteratorBoxBase< Element > @inlinable public init <I : IteratorProtocol >(_ base : I ) where I .Element == Element { self ._box = _IteratorBox(base) } } extension AnyIterator : IteratorProtocol { @inlinable public func next () -> Element ? { return _box.next() } } @_fixed_layout @usableFromInline internal class _AnyIteratorBoxBase <Element >: IteratorProtocol { @inlinable internal init () {} @inlinable deinit {} @inlinable internal func next () -> Element ? { _abstract() } } @_fixed_layout @usableFromInline internal final class _IteratorBox <Base : IteratorProtocol > : _AnyIteratorBoxBase <Base .Element > { @inlinable internal init (_ base : Base ) { self ._base = base } @inlinable deinit {} @inlinable internal override func next () -> Base .Element ? { return _base.next() } @usableFromInline internal var _base: Base }
整理一下,一共用到两个类,_AnyIteratorBoxBase
(基类) 和 _IteratorBox
(中转类)
基类符合协议,并且用一些占位符做好填充(需要在中转类中重写这些方法)
中转类继承自基类,并从外部接收一个符合协议的实例,用来重写(覆盖)基类中的方法和属性。
最后,用一个对外的 Type Eraser 再做一次中转。
仿制过程中的一些坑 1 2 3 4 5 6 7 8 9 10 11 12 protocol MyCollection { … }struct AnyMyCollection <Element > { … }extension AnyMyCollection : MyCollection { … }… struct Animals : MyCollection { … }struct Digits : MyCollection { … }var collections = [AnyMyCollection (Animals ()), AnyMyCollection (Digits ())]
首先,我注意到的是类型推断的问题,
可以尝试下 AnyCollection
,AnyIterator
这些,发现 collections
中的 Element 变成了 Any
,
也就引出了第一个问题。
Swift 的特性之一:类型推断 1 2 3 4 5 6 7 8 AnyIterator ([1 , 2 , 3 ].makeIterator())AnyIterator (["a" , "b" , "c" ].makeIterator())[AnyIterator ([1 , 2 , 3 ].makeIterator()), AnyIterator (["a" , "b" , "c" ].makeIterator())]
这俩东西的 Element 类型不一致,为什么放在一起还能编译并且 Element 变成 Any
了呢?
实际上,是 Swift 的类型推断在干活
而如果换一种写法:
1 2 3 4 let intIterator = AnyIterator ([1 , 2 , 3 ].makeIterator())let stringIterator = AnyIterator (["a" , "b" , "c" ].makeIterator())let together = [intIterator, stringIterator]
就会报错,原因就在于这两个东西的类型不一致,不能放在一个 Array 里,
提前声明的话,编译器会固化类型,是什么就是什么
AnyIterator<String>
和 AnyIterator<Int>
是两种不同的类型,它们的 Element 不同
更加详细的可以看我问的这个帖子:AnyCollection with different generic types
对 Array
再做一个扩展,让任何数组都符合协议。
1 2 3 4 5 extension Array : MyCollection { func allValues () -> [Element ] { self } } let arrayCollection = [AnyMyCollection ([1 , 2 , 3 ]), AnyMyCollection (["a" , "b" ])]
这里的两个类型分别是:AnyMyCollection<Int>
和 AnyMyCollection<String>
但是再加上 Digits
却报错了。
1 2 let collection = [AnyMyCollection (Digits ()), AnyMyCollection ([1 , 2 , 3 ]), AnyMyCollection (["a" , "b" ])]
这就是第二个问题。
编译时推断 和 运行时推断 分析一下每一个 AnyMyCollection
的类型:
1 2 3 4 5 6 7 8 AnyMyCollection (Digits ())AnyMyCollection ([1 , 2 , 3 ])AnyMyCollection (["a" , "b" ])
按照之前说的,应该统一成 AnyMyCollection<Any>
才对,
但是,仔细观察能够发现,传入 AnyMyCollection
的类型分别是 Digits
, Array<Int>
和 Array<String>
,
第一个不带泛型,而其他的均带泛型,
对于 Array<Element>
来说,编译时是可以确定 Element 的具体类型的,
且 Array
中的 Element
和 MyCollection
中的 Element
是一致的,
因此,编译时可以确定出 AnyMyCollection
中的 Element
(即 Array
中的 Element
)的类型。
但对于 Digits
来说,由于其本身没有泛型,所以 Element
事实上是被“隐藏”起来的,
编译器无法推断其类型,因此不能自动变成 Any
。
直到运行时才能确定出 Digits.Element
的具体类型是 Int
有解决办法嘛?有。
就是给 Digits
加上 Element 的泛型。
但是加上泛型再去限定只能是 Int
就有点脱裤子放屁的感觉了😂
最终我把它变成了一个 CustomCollection
1 2 3 4 5 6 7 8 9 10 11 12 struct CustomCollection <Element > { internal var content: [Element ] = [] init (_ content : Element ...) { self .content.append(contentsOf: content) } } extension CustomCollection : MyCollection { func allValues () -> [Element ] { content } }
AnyMyCollection 上面两个问题研究透彻之后,从 Protocol 到 Type Eraser 的仿制也就完成了。
最终代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 protocol MyCollection <Element > { associatedtype Element func allValues () -> [Element ] } struct AnyMyCollection <Element > { internal var _box: _AnyMyCollectionBase< Element > init <C : MyCollection >(_ base : C ) where Element == C .Element { _box = _MyCollectionBox(base) } } extension AnyMyCollection : MyCollection { func allValues () -> [Element ] { _box.allValues() } } final class _MyCollectionBox <Base : MyCollection >: _AnyMyCollectionBase <Base .Element > { init (_ base : Base ) { _base = base } private var _base: Base override func allValues () -> [Base .Element ] { return _base.allValues() } } class _AnyMyCollectionBase <Element >: MyCollection { func allValues () -> [Element ] { fatalError ("This method should be overwritten. Line 35." ) } } extension Array : MyCollection { func allValues () -> [Element ] { self } } struct CustomCollection <Element > { internal var content: [Element ] = [] init (_ content : Element ...) { self .content.append(contentsOf: content) } } extension CustomCollection : MyCollection { func allValues () -> [Element ] { content } } var collections = [AnyMyCollection (CustomCollection (1 , 2 , 3 )), AnyMyCollection (["a" , "b" ])]
总结 整个过程耗费了差不过两天的时间,最重要的是要理解这一种 Type Eraser 的基本原理和实现,
踩踩坑,更能加深印象🥲
不过 Swift 5.7 有了 any
关键字,不知道能不能取代掉 Type Eraser,期待一下 Swift 6。