30

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

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

不可变属性不会被解码,因为它声明了一个不能被覆盖的初始值

Xcode 建议将 更改let为 a var

修复:改为使属性可变

    var number: Int = 42

它还建议修复:

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

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

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

4

4 回答 4

48

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

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


  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. 将属性设为 a var,尽管您也可以将其设为 a private(set) var
struct ExampleItem: Decodable {
  var number: Int = 42
}

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


  1. 定义一个显式CodingKeys枚举:
struct ExampleItem: Decodable {
  let number: Int = 42
  
  private enum CodingKeys: CodingKey {}
}

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

于 2020-06-24T02:43:30.593 回答
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)

这实际上会打印出来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 的建议在许多情况下可能是可行的,但您应该根据具体情况评估将属性更改为 a 是否var会破坏您的应用程序的功能。

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

于 2020-06-24T00:37:46.767 回答
4

解决方案:定义一个显式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"
  }
}
于 2020-11-23T19:09:37.257 回答
1

@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"))
于 2021-12-16T09:43:06.170 回答