Swift 中的类型擦除(下)—— 从模仿中学习

Swift 中的类型擦除(下)—— 从模仿中学习

上篇博客中,我们初步了解了为什么需要一个 Type Eraser ,

也分享了一个简单实现 Type Erasure 的方案。

为了更加深入了解类型擦除,我们还是得来看看 Swift 自带的一些 Type Eraser 是如何实现的。

目标是:理解一下其基本思路,并且仿制一个出来。

底层原理

为了方便理解,我们聚焦于一个 Type Eraser:AnyIterator

为什么是它呢?

因为它出现在源码的最上面😂,而且下面的 AnySequenceAnyCollection 都得回到 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 // FIXME(sil-serialize-all)
internal init() {}

@inlinable // FIXME(sil-serialize-all)
deinit {}

@inlinable // FIXME(sil-serialize-all)
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 // FIXME(sil-serialize-all)
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
// MARK: - Protocol and Type Eraser
protocol MyCollection { }
struct AnyMyCollection<Element> { }
extension AnyMyCollection: MyCollection { }

// MARK: - Custom Types
struct Animals: MyCollection { }
struct Digits: MyCollection { }

// MARK: - Type Erasure
// ❌ Heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional
var collections = [AnyMyCollection(Animals()), AnyMyCollection(Digits())]

首先,我注意到的是类型推断的问题,

可以尝试下 AnyCollectionAnyIterator 这些,发现 collections 中的 Element 变成了 Any

也就引出了第一个问题。

Swift 的特性之一:类型推断

1
2
3
4
5
6
7
8
// Type: AnyIterator<Int>
AnyIterator([1, 2, 3].makeIterator())

// Type: AnyIterator<String>
AnyIterator(["a", "b", "c"].makeIterator())

// Type: [AnyIterator<Any>]
[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] // ❌ Heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional

就会报错,原因就在于这两个东西的类型不一致,不能放在一个 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<Any>

这里的两个类型分别是:AnyMyCollection<Int>AnyMyCollection<String>

但是再加上 Digits 却报错了。

1
2
// ❌ Heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional
let collection = [AnyMyCollection(Digits()), AnyMyCollection([1, 2, 3]), AnyMyCollection(["a", "b"])]

这就是第二个问题。

编译时推断 和 运行时推断

分析一下每一个 AnyMyCollection 的类型:

1
2
3
4
5
6
7
8
// AnyMyCollection<Digits.Element>
AnyMyCollection(Digits())

// AnyMyCollection<Int>
AnyMyCollection([1, 2, 3])

// AnyMyCollection<String>
AnyMyCollection(["a", "b"])

按照之前说的,应该统一成 AnyMyCollection<Any> 才对,

但是,仔细观察能够发现,传入 AnyMyCollection 的类型分别是 Digits, Array<Int>Array<String>

第一个不带泛型,而其他的均带泛型,

对于 Array<Element> 来说,编译时是可以确定 Element 的具体类型的,

Array 中的 ElementMyCollection 中的 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]
}

// MARK: - AnyMyCollection

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 }
}

// - MARK: Custom Collection

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。

Swift 中的类型擦除(下)—— 从模仿中学习

https://liyanan2004.github.io/type-erasure-in-swift-2/

作者

LiYanan

发布于

2023-01-16

更新于

2023-01-16

许可协议

评论