Xcode 警告:不可变 属性 将不会被解码,因为它是用无法覆盖的初始值声明的
Xcode warning: Immutable property will not be decoded because it is declared with an initial value which cannot be overwritten
运行 Xcode 12,我的 Swift 5 Xcode 项目现在在 Decodable
或 Codable
类型声明 let
具有初始值的常量。
struct ExampleItem: Decodable {
let number: Int = 42 // warning
}
Immutable property will not be decoded because it is declared with an initial value which cannot be overwritten
Xcode 建议将 let
更改为 var
:
Fix: Make the property mutable instead
var number: Int = 42
它还建议修复:
Fix: Set the initial value via the initializer or explicitly define a CodingKeys enum including a 'title' case to silence this warning
这个新警告的目的是什么?应该重视还是忽视?这种警告可以是吗?
是否应该实施 Xcode 的修复?或者有更好的解决方案吗?
出现此警告是因为具有初始值的不可变属性不参与解码 - 毕竟它们是不可变的并且具有初始值,这意味着初始值永远不会更改。
例如,考虑以下代码:
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
是否会中断您应用的功能。
如果您希望 属性 始终 return 硬编码初始值(这就是现在正在发生的事情),请考虑将其设为计算 属性 或惰性变量.
诺亚的解释是正确的。这是 bug 的常见来源,由于 Codable 合成的“神奇”行为,导致发生的事情不是很明显,这就是我 added this warning 编译器的原因,因为它让你注意到 属性 不会被解码,如果这是预期的行为,则让您明确调用它。
正如修复程序所解释的那样,如果您想消除此警告,您有几个选择 - 您选择哪个取决于您想要的确切行为:
- 通过
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
被解码。由于枚举没有大小写,因此编译器可以清楚地知道没有要解码的属性。
解决方案: 定义一个明确的 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"
}
}
@SuyashSrijan 建议的解决方法会抑制警告,但也可能导致进一步的开发人员错误。
我已经围绕 here:
写了一个替代方案
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"))
运行 Xcode 12,我的 Swift 5 Xcode 项目现在在 Decodable
或 Codable
类型声明 let
具有初始值的常量。
struct ExampleItem: Decodable {
let number: Int = 42 // warning
}
Immutable property will not be decoded because it is declared with an initial value which cannot be overwritten
Xcode 建议将 let
更改为 var
:
Fix: Make the property mutable instead
var number: Int = 42
它还建议修复:
Fix: Set the initial value via the initializer or explicitly define a CodingKeys enum including a 'title' case to silence this warning
这个新警告的目的是什么?应该重视还是忽视?这种警告可以是
是否应该实施 Xcode 的修复?或者有更好的解决方案吗?
出现此警告是因为具有初始值的不可变属性不参与解码 - 毕竟它们是不可变的并且具有初始值,这意味着初始值永远不会更改。
例如,考虑以下代码:
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
是否会中断您应用的功能。
如果您希望 属性 始终 return 硬编码初始值(这就是现在正在发生的事情),请考虑将其设为计算 属性 或惰性变量.
诺亚的解释是正确的。这是 bug 的常见来源,由于 Codable 合成的“神奇”行为,导致发生的事情不是很明显,这就是我 added this warning 编译器的原因,因为它让你注意到 属性 不会被解码,如果这是预期的行为,则让您明确调用它。
正如修复程序所解释的那样,如果您想消除此警告,您有几个选择 - 您选择哪个取决于您想要的确切行为:
- 通过
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
被解码。由于枚举没有大小写,因此编译器可以清楚地知道没有要解码的属性。
解决方案: 定义一个明确的 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"
}
}
@SuyashSrijan 建议的解决方法会抑制警告,但也可能导致进一步的开发人员错误。 我已经围绕 here:
写了一个替代方案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"))