Indeterminate Types with Encodable in Swift

2020-05-29
摘要: 本文介绍了一种为不确定类型实现 Encodable 协议的方法

问题描述#

最近在用 Swift 做一个简单的后端项目来完成课程作业。很自然地我们需要定义一个 Model 来描述我们返回的数据,比较常见的就是定义一个 ResultEntity,它由 code, message 和 data 构成。其中 code 反应请求结果是否成功/失败原因,message 为附带的文字描述,data 为附带的数据。

对于 Swift 的后端框架(例如 Vapor, Perfect)来说,一个对象/结构体想要被自动编码/解码,对象的类/结构体必须遵循 Codable(Encodable & Decodable) 协议。对于绝大多数情况,我们只需要声明类/结构体遵循 Codable 即可,Swift 会自动为我们完成协议的实现(因为基本类型都是 Codable的)。对于需要自定义被编码/解码字段的情况,我们可以手动实现 encode(to:)decode(to:) 方法。

但是当我们需要一个遵循 Encodable 协议的结构体/类,并且某个 Property 是类型不确定的 Encodable 结构体/对象时,Swift 无法为我们自动实现 Encodable 协议。而且当我们手动实现 encode(to:) 方法时,encode 函数提示因为我们提供的是一个 protocol type,它并不遵循 Encodable 协议(当然 :D )。所以问题在于我们声明的 data: Encodable 是一个 protocol type。

采用泛型#

首先我们想到可以通过泛型解决这个问题:

但是这样的话我们在函数声明时必须指定返回的 ResultEntity 的类型,而其中的 data 的类型是任意的,而函数中可能由于执行结果不同而返回不同类型的 ResultEntity,这样的做法自然不太妥当。

采用泛型与闭包#

在谷歌上搜索 “Value of protocol type ‘Encodable’ cannot conform to ‘Encodable’; only struct/enum/class types can conform to protocols”,可以找到一篇文章:Indeterminate Types with Codable in Swift。文章中针对作者遇到的情况提出了两种解决方案(文章中是的类型变化范围是不经常但是有可能发生变化的,而且主要是针对解码)。

在受其启发后,我们提出如下方式:

因为一旦我们记录 data 的类型,ResultEntity 必然需要使用泛型,所以我们保留 data 为 Encodable。

我们采用手动实现协议的方式(声明一个遵循 CodinigKey 的枚举类型,同时实现 encode(to:) 方法)。我们将 init 设置为泛型函数,它接受任何遵循 Encodable 协议的类型 T。于是当我们初始化 ResultEntity 时,我们可以便获得 data 的类型 T,进而将对 data 的编码操作存储为一个闭包,在 encode(to:) 时进行调用。