快速浏览类型擦除
如果您正在编写Swift,那么您可能已经听说过类型擦除。 也许您甚至在使用它。 但是,即使您属于这两个阵营中的一个,也有可能实际上不知道为什么类型擦除有效。 ♂️
与其给您提供有关如何擦除类型的方法,不如让我们实际研究问题所在并逐步解决。
问题
假设我们正在制作一个通用网络会话类,该类必须支持各种JSON反序列化器,这些反序列化器返回的对象与网络会话的关联类型匹配。
像这样:
这很烦人,但确实有道理。 我们的协议JsonDeserializer
是抽象类型,因此Swift编译器无法推断其引用的具体类型,也不能假设其在编译时由其associatedtype
提供的类型。
Hector Matos在他的博客KrakenDev
:
简短的答案是:Swift希望是类型安全的。 再加上它是一种提前编译的语言,并且您有一种需要NEEDS能够在编译期间随时推断具体类型的语言。 我不能太强调。 在编译时,不是函数/类约束的每个类型都需要具体。 协议中的关联类型是抽象的。 这意味着它们不是具体的。 他们是假的。 没有人喜欢假货。
因此,尽管此错误令人不快,但确实有道理-并且该错误告诉我们一些重要的信息。 指出,该协议只能用作通用约束 ,即在尖括号之间。
尝试1
我们知道,将JsonDeserializer
用作类型约束的唯一方法是在使用它,但是出于参数的考虑,我们决定使用协议约束来指定一种类型,就像我们对泛型类的处理方式一样。 结果如何?
Cannot specialize non-generic type 'JsonDeserializer'
❌
虽然这是预期的。 正如我们前面所讨论的,该协议是一种抽象类型,尽管它可能具有通用要求,但它本身并不是通用类型。 不是class SomeType { ... }
方式。
尝试2
好吧,让我们尝试使用JsonDeserializer
作为一般约束,并将反序列化器放入一个代理对象,我们将其称为DeserializerBox
(此处使用Box
是为了与以后的类型擦除命名约定保持一致)。
这个错误告诉我们我们需要提供一种类型来满足DeserializerBox
的通用类型要求。 不幸的是,这意味着要指定一个JsonDeserializer
。 但是,我们想使用任何为我们提供Payload
类型对象的JsonDeserializer
,因此指定特定的反序列化器具体类型将无法达到目的。 这与仅将具体类型用作Session
的deserializer
属性的类型约束没有什么不同。 令人沮丧的是,这是进步。
尝试3和3.5ish
为了弥补我们缺乏必要的类型争论,我们还需要DeserializerBox
成为JsonDeserializer
的真正代理对象,这意味着它应与JsonDeserializer
。 这意味着我们应该能够将代理与JsonDeserializer
一样对待,并且它应该公开相同的接口。 为此,我们必须坚持使用JsonDeserializer
,包装内部的具体JsonDeserializer
,然后使用蹦床协议属性和对具体反序列化器的方法调用。
但是 ,我们还没有完成……我们在上一次尝试中看到,使用JsonDeserializer
作为通用约束要求我们为Box
对象提供一个适得其反的具体类型,所以让我们尝试使用Payload
类型,例如T
,我们的通用约束,让我们坚持使用JsonDeserializer
。
在不弄乱DeserializerBox
(我们将回到这一点),让我们围绕具体的反序列化器定义一个简单的包装器:
我们再次引用JsonDeserializer
而不将其用作一般约束,因此我们知道这将失败。 让我们对其进行更新以使用通用约束并查看会发生什么:
现在公开了,这可能不是一个好主意。 通过暴露该约束,我们将不可避免地与尝试2处于同一位置,在该位置我们必须指定特定的
JsonDeserializable
来满足通用要求。 如果我们可以把那个需求埋在某个地方……让我们在尝试4中讨论它。
尝试4 –结合想法💡
我们所知道的:
- 我们需要能够存储一个反序列化器,这意味着我们需要具有
generic constraint
- 但是我们需要
SomeDeserializer
Box
具有通用要求的事实,并且需要像对SomeDeserializer
V1所做的那样公开代理对象 ,我们所知道的就是具有generic type
如果只有在隐藏内部deserializer
DeserializerBox
做得很好的DeserializerBox
可以具有SomeDeserializer
的签名和JsonDeserializer
行为。 但是可以 !
我们正在描述的场景是继承(子类化) ! 我们只需要定义一个抽象类,例如BaseJsonDeserializer: JsonDeserializer
,当它由DeserializerBox
子类DeserializerBox
将赋予它JsonDeserializer
所有可JsonDeserializer
行为,最JsonDeserializer
,它允许DeserializerBox
藏在后面它是超类声明BaseJsonDeserializer
。
让我们继续定义这些类:
清理
现在,我们有了一种机制,可以引用任何特定预期Payload
类型的JsonDeserializer
。 我们只是将实例存储为BaseJsonDeserializer
… 是吗?
好吧,如果您熟悉类型擦除模式,那么您已经知道通常包含三个组件:
- 抽象基类✅
- 私人信箱✅(我们还不是私人的)
- 公共包装❓
第三部分是对我们问题的回答,为什么不只使用BaseJsonDeserializer
? BaseJsonDeserializer
类是一个抽象类,并且绝对不应直接初始化一个抽象类,因为该抽象类只能在那里为其子类建立可继承的结构。 因此, BaseJsonDeserializer
不应成为我们面向public
的对象—如果不应该直接使用该类,我们应该注意不要公开公开该类。
这就是Public wrapper
起作用的地方。 在整个本文中,我一直在提到代理对象-这是代理设计模式的结果。 我们称为AnyJsonDeserializer
Public wrapper
将成为我们的实际代理对象。 它将遵循JsonDeserializer
,为其提供与具体JsonDeserializer
相同的接口,但实际上将转发/蹦床方法及其属性调用对其DeserializerBox
对象。 实现最后一个组件,我们的最终代码如下所示:
资源📚
- https://krakendev.io/blog/generic-protocols-and-their-shortcomings
- https://www.bignerdranch.com/blog/breaking-down-type-erasures-in-swift/
- https://sourcemaking.com/design_patterns/proxy
这样就可以了,从头到尾键入擦除。 使用类型擦除非常棒,但是更好地了解其工作方式和原因!
如果您喜欢阅读的内容,可以随时喜欢,分享和订阅🙌🏾
谢谢阅读!
☮️❤️