此软件包定义了在解码 Decodable 类型时,从错误中部分恢复的机制。它还旨在提供符合人体工程学的 API,以便在开发期间检查解码错误,并在生产环境中报告它们。
更多细节如下,但这里简要介绍一下此软件包的功能
struct Foo: Decodable {
@Resilient var array: [Int]
@Resilient var value: Int?
}
let foo = try JSONDecoder().decode(Foo.self, from: """
{
"array": [1, "2", 3],
"value": "invalid",
}
""".data(using: .utf8)!)
运行此代码后,foo 将是一个 Foo 类型,其中 foo.array == [1, 3] 且 foo.value == nil。在 DEBUG 模式下,foo.$array.results 将会是 [.success(1), .failure(DecodingError.dataCorrupted(…), .success(3)],并且 foo.$value.error 将会是 DecodingError.dataCorrupted(…)。此功能仅在 DEBUG 模式下可用,以便我们可以保持 在发布版本中没有额外开销。
在你的 Package.swift 文件中
dependencies: [
.package(name: "ResilientDecoding", url: "https://github.com/airbnb/ResilientDecoding.git", from: "1.0.0"),
]
在你的 Podfile 文件中
platform :ios, '12.0'
pod 'ResilientDecoding', '~> 1.0'
此软件包的主要接口是 @Resilient 属性包装器。它可以应用于四种类型的属性:Optional、Array、Dictionary 和符合此软件包提供的 ResilientRawRepresentable 协议的自定义类型。
Optionals 是可以被赋予 Resilient 特性的最简单类型的属性。 声明为 @Resilient var foo: Int? 的属性将被初始化为 nil,如果在解码期间遇到错误(例如,如果 foo 键的值是 String),则不会抛出错误。
Resilient 也可以应用于数组或可选数组 ([T]?)。 声明为 @Resilient var foo: [Int] 的属性,如果 foo 键缺失,或者该值是意外的值(例如 String),则将初始化为空数组。 同样,如果此数组的任何元素解码失败,则该元素将被省略。 此可选数组变体将在键丢失或具有空值时将值设置为 nil,否则设置为一个空数组。
Resilient 也可以应用于(字符串键控的)字典或可选字典 ([String: T]?)。 声明为 @Resilient var foo: [String: Int] 的属性,如果 foo 键缺失,或者该值是意外的值(例如 String),则将初始化为空字典。 同样,如果字典中的任何值解码失败,则该值将被省略。 此可选字典变体将在键丢失或具有空值时将值设置为 nil,否则设置为一个空字典。
自定义类型可以遵循 ResilientRawRepresentable 协议,该协议允许它们在作为 Resilient 属性解码时自定义其行为(否则没有影响)。 ResilientRawRepresentable 继承自 RawRepresentable,主要用于原始值的 enum 遵循。 ResilientRawRepresentable 有两个静态属性:decodingFallback 和 isFrozen。
ResilientRawRepresentable 类型可以选择定义 decodingFallback,这使其可以在不包装在 optional 中的情况下进行弹性解码。 例如,以下枚举可以在编写为 @Resilient var myEnum: MyEnum 的属性中使用
enum MyEnum: String, ResilientRawRepresentable {
case existing
case unknown
static var decodingFallback: Self { .unknown }
}
注意: ResilientRawRepresentable 的 Array 和 Dictionary 总是省略元素,而不是使用 decodingFallback。
isFrozen 控制新的 RawValues 是否会将错误报告给 ResilientDecodingErrorReporter。 默认情况下,isFrozen 为 false,这意味着 init(rawValue:) 返回 nil 的 RawValue 不会报告错误。 当你希望旧版本的代码支持新的 enum 情况而不报告错误时,这非常有用,例如,当演进 iOS 应用程序使用的后端 API 时。 通过这种方式,该属性类似于 Swift 的 @frozen 属性,尽管它们实现了不同的目标。 isFrozen 对属性级别的错误没有影响。
Resilient 提供了两种检查错误的机制,一种设计用于开发期间,另一种设计用于报告生产环境中意外的错误。
在 DEBUG 版本中,Resilient 属性提供了一个 projectedValue,其中包含有关解码期间遇到的错误的信息。可以使用 $property.outcome 属性检查此信息,这是一个枚举,其 cases 包括 keyNotFound 和 valueWasNil。这与错误不同,因为上述两种情况在属性值为 Optional 时实际上并不是错误。标量类型(例如 Optional 和 ResilientRawRepresentable)还提供了一个 error 属性。开发人员可以通过访问编写为 @Resilient var foo: Int? 的属性的 $foo.error 来确定解码期间是否发生错误。 @Resilient 数组属性提供了两个附加字段:errors 和 results。 errors 是解码数组时恢复的所有错误的列表。 results 将这些错误与已成功解码的数组元素交错在一起。 例如,在解码 JSON 代码段 [1, 2, "3"] 时,编写为 @Resilient var baz: [Int] 的属性的 results 将是两个 .success 值,后跟一个 .failure。
在生产中,ResilientDecodingErrorReporter 可用于整理使用 Resilient 属性解码类型时遇到的所有错误。 JSONDecoder 提供了一个方便的 decode(_:from:reportResilientDecodingErrors:) API,如果遇到错误,该 API 返回解码后的值和错误摘要。 更复杂的用例需要在你的 Decoder 的 userInfo 中添加一个 ResilientDecodingErrorReporter,作为 .resilientDecodingErrorReporter 用户信息键的值。 解码类型后,你可以调用 flushReportedErrors,如果遇到任何错误,它将返回一个 ErrorDigest。 该摘要可用于访问底层错误 (errorDigest.errors) 或在 DEBUG 中进行漂亮的打印 (debugPrint(errorDigest))。
漂亮打印的摘要看起来像这样
resilientArrayProperty
Index 1
- Could not decode as `Int`
Index 3
- Could not decode as `Int`
resilientRawRepresentableProperty
- Unknown novel value "novel" (this error is not reported by default)
注意:属性包装器上可用的错误与报告给 ResilientDecodingErrorReporter 的错误之间的一个区别是,后者默认情况下不报告 UnknownNovelValueErrors(当非冻结的 ResilientRawRepresentable 的 init(rawValue:) 返回 nil 时,会抛出 UnknownNovelValueError)。 你可以通过在错误摘要上调用 errors(includeUnknownNovelValueErrors: true) 来更改此行为。
否。 如果你的类型是关于 <T> 的泛型,并且指定 @Resilient var someResilient: T,那么无论 T 是数组还是字典,都将被视为单个值。
我们没有明确地让 Resilient 符合 Encodable,因为在存在错误的情况下,编码可能会有损。 如果你确定这对于你的用例不是问题,那么在你自己的模块中提供一个 Encodable 一致性应该很简单。
有关特定 Resilient 字段在遇到特定错误时究竟如何表现的更多信息,我建议查阅单元测试。