使用可编码对带有关联值的枚举进行编码和解码(Swift 4)

在下载管理器框架的Swift 4迁移过程中,我重写了模型对象(符合NSCoding NSObject子类)。 我的DownloadItem的状态由带有关联值的枚举表示:

使用Swift 3,我必须编写一个符合NSCoding的包装对象,以保留该枚举。 使用Codable不再需要此解决方法,因为我可以直接向枚举添加Codable一致性。 现在,有不同的方法可以实现此目的。 我仍然不确定最好的解决方案是什么,但是也许有人可以使用我目前的方法。

在查看DownloadState ,让我们看一下一个简单的Value枚举示例:

在类型和大小写之间存在一对一的映射,因此我们可以设置一个如下所示的容器:

  //伪字典 
[“ string”:字符串,“ int”:Int,“ data”:数据,“ double”:Double]

毕竟, KeyedDecodingContainerKeyedEncodingContainer只是非常严格的字典,其中下标键是强类型的String枚举(符合CodingKey ),而getter和setter利用Swift的错误处理系统来提供有意义的错误,说明为什么获取或设置特定键的值可能失败。 因此,至少对我而言,将容器视为具有固定键的字典是有意义的🙂

Value实现Codable可能看起来像这样:

我们使用decodeIfPresent函数,因为将只设置一个密钥。 缺点是我们不使用switch语句进行解码,因此当我们添加新的大小写而忘记对其进行解码时,编译器不会对我们大喊大叫。

而且,它不适用于DownloadState示例,因为我们有带有和不带有关联值的案例。 我的方法是使用“基本”案例和关联值作为CodingKey ,然后让一个私有枚举将所有基本案例作为简单String托管。 让我们看看它的外观:

我真正喜欢的是编译器可以完全自动生成BaseCodable实现,因为它只是一个String枚举。 不幸的是,我们仍然必须为DownloadState provide提供Codable实现。

以下伪字典都是DownloadState所有有效表示形式。

  //伪字典 
[“基地”:“取消”]
->下载状态。取消
  [“ base”:“ paused”,“ pausedReason”:“ waitingForWifi”] 
-> DownloadState.paused(原因:.waitingForWifi)
  [“基础”:“下载”,“下载进度”:0.5] 
-> DownloadState.downloading(进度:0.5]

可以缩放吗? 不……如果涉及多个关联值,则实际上这是一个非常复杂的类型,应像嵌套结构或类一样进行处理。 请考虑以下示例,其中Route表示带有主页和详细信息页面(用于播放音乐)的音乐应用程序的不同屏幕:

超级简单吧? 但是将每个关联的值添加到CodingKeys

有一些缺点:

  • 在与基本情况相同的层次结构上具有关联值在语义上是错误的(就像在DownloadState示例中一样,但是更加突出)
  • 如果枚举得到更多的情况(和更多的关联值),这将变得更加难以阅读和维护
  • 如果这是一个类或结构,则您永远不会考虑对父代中嵌套类型的信息进行编码。

那么我们该如何解决呢? 我认为为每个具有关联值的案例添加结构是一种干净的方法,有助于分担责任:

由于我们将Codable一致性委托给每个关联值结构,因此CodingKeys每种情况下(与关联值)仅包含一个键。 该代码看起来很重复,但是很简单并且遵循一定的模式。 您也可以使用Sourcery来自动执行此过程。

它也适用于使用Codable类型作为关联值的枚举:

同样,我们为基本案例提供了一个密钥,为每个案例提供了一个具有关联值的编码密钥。 这些结构自行处理编码和解码,并且结构内的类型本身负责实现Codable

祝你开心!