运行 Xcode 12,我的 Swift 5 Xcode 项目现在每当Decodable
或Codable
类型声明具有初始值的let
常量时都会发出警告。
struct ExampleItem: Decodable {
let number: Int = 42 // warning
}
不可变属性不会被解码,因为它是用无法覆盖的初始值声明的
Xcode 建议将let
更改为var
:
解决方法:改为使属性可变
var number: Int = 42
它还建议修复:
修复:通过初始值设定项设置初始值,或显式定义包含"title"大小写的 CodingKeys 枚举以消除此警告
这个新警告的目的是什么?它应该被注意,还是被忽视?这种类型的警告可以静音吗?
Xcode的修复应该实施吗?还是有更好的解决方案?
诺亚的解释是正确的。这是错误的常见来源,由于 Codable 合成的"神奇"行为,它并没有立即明显地看到发生了什么,这就是我在编译器中添加此警告的原因,因为它提请您注意该属性不会被解码的事实,并让您明确调用它,如果这是预期的行为。
正如fix-it所解释的那样,如果您想消除此警告,您有几个选择 - 选择哪一个取决于您想要的确切行为:
- 通过
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")
,即使我们给它的 jsonvalue
"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
。
建议的解决方法 @SuyashSrijan 禁止显示警告,但也可能导致进一步的开发人员错误。 我在这里写了一个替代作品:
public struct IdentifierWrapper<T>: Identifiable {
public let id = UUID()
public let value: T
}
用法:
struct Model: Codable, Identifiable {
public let name: String
}
let wrapper = IdentifierWrapper(value: Model(name: "ptrkstr"))