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

pka*_*amb 28 xcode warnings swift xcode12

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

struct ExampleItem: Decodable {
    let number: Int = 42 // warning
}
Run Code Online (Sandbox Code Playgroud)

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

Xcode 建议将 更改let为 a var

修复:改为使属性可变

    var number: Int = 42
Run Code Online (Sandbox Code Playgroud)

它还建议修复:

修复:通过初始化程序设置初始值或明确定义一个 CodingKeys 枚举,包括一个“标题”案例以消除此警告

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

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

小智 42

诺亚的解释是正确的。这是错误的常见来源,并且由于 Codable 综合的“神奇”行为,发生的事情并不是很明显,这就是我编译器添加此警告的原因,因为它使您注意到该属性不会解码并让您明确调用它,如果这是预期的行为。

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


  1. 通过 传递初始值init
struct ExampleItem: Decodable {
  let number: Int
    
  init(number: Int = 42) {
    self.number = number
  }
}
Run Code Online (Sandbox Code Playgroud)

这将允许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
  }
}
Run Code Online (Sandbox Code Playgroud)

这将允许number解码,但42如果解码失败,则用作默认值。


  1. 将属性设为 a var,但您也可以将其设为a private(set) var
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
  }
}
Run Code Online (Sandbox Code Playgroud)

使它成为 avar将允许number被解码,但它也将允许调用者修改它。通过将其标记为private(set) var代替,您可以根据需要禁止此操作。


  1. 定义一个显式CodingKeys枚举:
struct ExampleItem: Decodable {
  var number: Int = 42
}
Run Code Online (Sandbox Code Playgroud)

这将防止number被解码。由于枚举没有大小写,这让编译器清楚地知道没有要解码的属性。

  • 我们的用例是我们有可编码的结构,在编码时值应该始终是特定的字符串,在解码时并不重要,值仍然需要相同,所以实际上我们不能从 CodingKeys 中省略它们因为这样它们就不会被编码。我真的不认为警告应该出现在此处,因为它只是向开发人员提供信息并且没有真正的方法来抑制它 (4认同)
  • 很高兴从实施新错误的人那里得到答案。我能问一下,你是如何这么快找到这个问题的,而且是一个新的 SO 帐户?! (3认同)
  • 是否考虑过禁用此警告?在某些情况下这是可取的,但现在我们看到了有效代码的警告。我们了解 Codable 的工作原理,并且希望有一种方法可以在没有警告的情况下使用正确的 swift 代码。(我可以将其与discardableResult进行比较,它允许调用者忽略结果而不生成警告)编写自己的编码键比依赖编译器生成的代码更容易出错。(一般来说,代码行数越少越好) (3认同)
  • 我同意。我有一个用例,我不希望出现此警告。`let id = UUID()` 该结构体中包含更多变量。似乎很奇怪,我必须明确创建 init/或枚举只是为了沉默我需要的东西......也许可以用另一种方式处理它? (3认同)
  • 我看到了,但是我们希望的行为是将值编码到 json 中,但不进行解码。如果您考虑自动生成的代码的外观,那么 Codable 的工作原理就很有意义了。但令我烦恼的是,我们无法让编译器生成的代码遵循异构 json 数组的相当常见的约定。(我希望我听起来没有太咆哮) (2认同)
  • 老实说,编写 CodingKeys 并不那么令人满意。通常,消除警告需要执行一些简单的操作,例如添加“as Any”或在某些内容周围添加括号。写出 CodingKeys 更加复杂,并且意味着将来添加的字段可能会被遗忘。如果斯威夫特有一个适当的系统来抑制警告,那就没问题了。现在我必须写出大约 70 个结构的 CodingKeys... (2认同)
  • 哦,这显然是一个错误的决定。为什么我们应该对完全有效且工作的代码发出警告? (2认同)
  • 因此,我通过将 let 转换为 var 来实现“修复”之一。所有编译都正常,但后来发现了与 Realm 对象(变量和集合)相关的问题,这实际上破坏了应用程序的功能。所以,是的,实施修复,但要非常小心,因为它们可能会产生副作用。 (2认同)
  • 也只是在这里添加。这是一个非常愚蠢的警告,特别是现在我们需要我们的 Codable 也符合 Identificable。也许需要为此创建一个属性包装器。即使 LET 应该绰绰有余...... (2认同)

Noa*_*ore 5

出现此警告是因为具有初始值的不可变属性不参与解码 - 毕竟,它们是不可变的并且它们具有初始值,这意味着初始值永远不会改变。

例如,考虑以下代码:

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)
Run Code Online (Sandbox Code Playgroud)

这实际上会打印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")"
Run Code Online (Sandbox Code Playgroud)

将值更改为 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")"
Run Code Online (Sandbox Code Playgroud)

如果您看到此错误,则表示您的代码在解码时从未正确解析相关属性。如果您将其更改为 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)!)
Run Code Online (Sandbox Code Playgroud)

总之,Xcode 的建议在许多情况下可能是可行的,但您应该逐案评估将属性更改为 a 是否var会破坏您的应用程序的功能。

如果您希望属性始终返回硬编码的初始值(这就是现在发生的情况),请考虑将其设为计算属性或惰性变量。