运行 Xcode 12,我的 Swift 5 Xcode 项目现在只要
Decodable
或 Codable
类型声明具有初始值的 let
常量就会发出警告。
struct ExampleItem: Decodable {
let number: Int = 42 // warning
}
不可变属性不会被解码,因为它是用不能被覆盖的初始值声明的
Xcode 建议将
let
更改为 var
:
修复:改为使属性可变
var number: Int = 42
它还建议修复:
修复:通过初始化程序设置初始值或显式定义一个包含“标题”大小写的 CodingKeys 枚举来消除此警告
这个新警告的目的是什么?是应该重视,还是忽视?这种类型的警告可以被静音吗?
Xcode 的修复是否应该实施?或者有更好的解决办法吗?
诺亚的解释是正确的。这是错误的常见来源,并且由于可编码合成的“神奇”行为,发生的情况并不是很明显,这就是为什么我向编译器添加了此警告,因为它让您注意到该属性不会不会被解码并让您明确地调用它(如果这是预期的行为)。 正如修复程序所解释的那样,如果您想消除此警告,您有多种选择 - 您选择哪一个取决于您想要的确切行为:
init
struct ExampleItem: Decodable {
let number: Int
init(number: Int = 42) {
self.number = number
}
}
这将允许解码
number
,但您也可以传递使用默认值的
ExampleItem
实例。您也可以直接在init
内部使用它,在解码过程中:
struct ExampleItem: Decodable {
let number: Int
private enum CodingKeys: String, CodingKey {
case number
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
number = try container.decodeIfPresent(Int.self, forKey: .number) ?? 42
}
}
这将允许解码
number
,但如果解码失败,则使用
42
作为默认值。
var
private(set) var
:
struct ExampleItem: Decodable {
var number: Int = 42
}
将其设为
var
将允许
number
被解码,但也允许调用者对其进行修改。通过将其标记为 private(set) var
,您可以根据需要禁止此操作。
CodingKeys
struct ExampleItem: Decodable {
let number: Int = 42
private enum CodingKeys: CodingKey {}
}
这将阻止
number
被解码。由于枚举没有大小写,这使编译器清楚地知道没有要解码的属性。
例如,考虑以下代码:
struct Model: Decodable {
let value: String = "1"
}
let json = """
{"value": "2"}
"""
let decoder = JSONDecoder()
let model = try! decoder.decode(Model.self, from: json.data(using: .utf8)!)
print(model)
这实际上会打印
Model(value: "1")
,即使我们给它的 json 的
value
为 "2"
。事实上,您甚至不需要提供正在解码的数据中的值,因为它无论如何都有一个初始值!
let json = """
{}
"""
let decoder = JSONDecoder()
let model = try! decoder.decode(Model.self, from: json.data(using: .utf8)!)
print(model) // prints "Model(value: "1")"
将值更改为 var 意味着它将正确解码:
struct VarModel: Decodable {
var value: String = "1"
}
let json = """
{"value": "2"}
"""
let varModel = try! decoder.decode(VarModel.self, from: json.data(using: .utf8)!)
print(varModel) // "VarModel(value: "2")"
如果您看到此错误,则意味着您的代码在解码时从未正确解析有问题的属性。如果将其更改为 var,该属性将被正确解析,这可能就是您想要的 - 但是,您应该确保您正在解码的数据始终具有该键集。例如,这将引发异常(并且由于我们使用的是
try!
而崩溃):
let json = """
{}
"""
let decoder = JSONDecoder()
struct VarModel: Decodable {
var value: String = "1"
}
let varModel = try! decoder.decode(VarModel.self, from: json.data(using: .utf8)!)
总之,Xcode 的建议在许多情况下可能是可行的,但您应该根据具体情况进行评估,将属性更改为
var
是否会破坏应用程序的功能。
如果您希望属性始终返回硬编码的初始值(这就是现在正在发生的情况),请考虑将其设为计算属性或惰性变量。
定义一个显式的CodingKeys
枚举以防止
id
被解码。
例如,struct Course: Identifiable, Decodable {
let id = UUID()
let name: String
private enum CodingKeys: String, CodingKey {
case name
}
init(name: String) { self.name = name }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let name = try container.decodeIfPresent(String.self, forKey: .name)
self.name = name ?? "default-name"
}
}
struct ExampleItem: Decodable {
var number: Int { 42 } // no warning anymore
}
行为保持不变 - 解码器不会干扰
number
字段。而且您不需要实现自定义
CodingKeys
。