Xcode 警告:不可变属性不会被解码,因为它是使用无法覆盖的初始值声明的



运行 Xcode 12,我的 Swift 5 Xcode 项目现在每当DecodableCodable类型声明具有初始值的let常量时都会发出警告。

struct ExampleItem: Decodable {
let number: Int = 42 // warning
}

不可变属性不会被解码,因为它是用无法覆盖的初始值声明的

Xcode 建议将let更改为var

解决方法:改为使属性可变

var number: Int = 42

它还建议修复:

修复:通过初始值设定项设置初始值,或显式定义包含"title"大小写的 CodingKeys 枚举以消除此警告

这个新警告的目的是什么?它应该被注意,还是被忽视?这种类型的警告可以静音吗?

Xcode的修复应该实施吗?还是有更好的解决方案?

诺亚的解释是正确的。这是错误的常见来源,由于 Codable 合成的"神奇"行为,它并没有立即明显地看到发生了什么,这就是我在编译器中添加此警告的原因,因为它提请您注意该属性不会被解码的事实,并让您明确调用它,如果这是预期的行为。

正如fix-it所解释的那样,如果您想消除此警告,您有几个选择 - 选择哪一个取决于您想要的确切行为:


  1. 通过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作为默认值。


  1. 使属性成为var,尽管您也可以将其设为private(set) var
struct ExampleItem: Decodable {
var number: Int = 42
}

使其成为var将允许解码number,但也允许调用方对其进行修改。通过将其标记为private(set) var,您可以根据需要禁止此操作。


  1. 定义显式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"))

最新更新