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 项目现在在 DecodableCodable 类型声明 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 编译器的原因,因为它让你注意到 属性 不会被解码,如果这是预期的行为,则让您明确调用它。

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


  1. 通过 init:
  2. 传递初始值
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 被解码。由于枚举没有大小写,因此编译器可以清楚地知道没有要解码的属性。

解决方案: 定义一个明确的 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"))