快速浏览类型擦除

如果您正在编写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 ,因此指定特定的反序列化器具体类型将无法达到目的。 这与仅将具体类型用作Sessiondeserializer属性的类型约束没有什么不同。 令人沮丧的是,这是进步。

尝试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是吗?

好吧,如果您熟悉类型擦除模式,那么您已经知道通常包含三个组件:

  • 抽象基类✅
  • 私人信箱✅(我们还不是私人的)
  • 公共包装❓

第三部分是对我们问题的回答,为什么不只使用BaseJsonDeserializerBaseJsonDeserializer类是一个抽象类,并且绝对不应直接初始化一个抽象类,因为该抽象类只能在那里为其子类建立可继承的结构。 因此, 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

这样就可以了,从头到尾键入擦除。 使用类型擦除非常棒,但是更好地了解其工作方式和原因!

如果您喜欢阅读的内容,可以随时喜欢,分享和订阅🙌🏾
谢谢阅读!

☮️❤️