此软件包定义了在解码 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
的错误之间的一个区别是,后者默认情况下不报告 UnknownNovelValueError
s(当非冻结的 ResilientRawRepresentable
的 init(rawValue:)
返回 nil
时,会抛出 UnknownNovelValueError
)。 你可以通过在错误摘要上调用 errors(includeUnknownNovelValueErrors: true)
来更改此行为。
否。 如果你的类型是关于 <T>
的泛型,并且指定 @Resilient var someResilient: T
,那么无论 T
是数组还是字典,都将被视为单个值。
我们没有明确地让 Resilient
符合 Encodable
,因为在存在错误的情况下,编码可能会有损。 如果你确定这对于你的用例不是问题,那么在你自己的模块中提供一个 Encodable
一致性应该很简单。
有关特定 Resilient
字段在遇到特定错误时究竟如何表现的更多信息,我建议查阅单元测试。