我有一个非常简单的应用程序。它从 JSON 文件加载程序员数据,对其进行解码并在文本视图中显示程序员的姓名、地址和城市。
struct DecodeJson: View {
@State var decodedText: String = ""
@State var encodedText: String = ""
@State var programmers = Programmers()
var body: some View {
VStack {
Button("Load JSON") {
decodedText = ""
programmers = load("programmers.json")
for programmer in programmers.items {
decodedText.append("\(programmer.name), \(programmer.address.street), \(programmer.address.city)\n")
}
}
Text(decodedText)
}
}
}
class Programmers: Codable, Identifiable {
var items: [Programmer] = [
Programmer(id: 1, name: "Programmer1", address: Address(street: "Street1", city: "City1"), changed: false),
Programmer(id: 2, name: "Programmer2", address: Address(street: "Street2", city: "City2"), changed: false),
]
}
class Programmer: Codable, Identifiable {
var id: Int
var name: String
var address: Address
var changed: Bool
init(id: Int, name: String, address: Address, changed: Bool) {
self.id = id
self.name = name
self.address = address
self.changed = changed
}
}
class Address: Codable, Identifiable {
var street: String
var city: String
init(street: String, city: String) {
self.street = street
self.city = city
}
}
这效果很好,但我需要一个程序员编辑器视图,而不是在简单的文本框中显示程序员数据,因为我想编辑程序员并将更改保存回 JSON 文件。所以我需要将程序员的姓名和地址双向绑定到编辑器视图。我应该使用 @Observale 包装器包装 3 个类,并在编辑器视图中使用 @Bindable。
问题是,当我将 @Observale 包装器添加到 Address 类并构建应用程序时,我会收到一个模糊的警告:不可变属性将不会被解码,因为它是用无法覆盖的初始值声明的 .
// Added @Observable
@Observable class Address: Codable, Identifiable {
var street: String
var city: String
init(street: String, city: String) {
self.street = street
self.city = city
}
}
如果我使用 @Observale 包装类运行应用程序,那么它不会加载 JSON 文件,即使它报告 JSON 文件中的错误,该文件在没有 @Observale 包装器的情况下加载正常...
有人懂这个吗? 感谢您的帮助!
我重写了应用程序,至少10次,我尝试在Apple甚至stackoverflow上搜索错误消息,但我找不到任何解决方案。我需要帮助。谢谢大家!
Codable
一致性的综合发生在宏扩展之后,因此错误消息实际上是在谈论由@Observable
宏生成的代码,这就是它如此令人困惑的原因。也就是说,它正在谈论观察登记员:
@ObservationIgnored private let _$observationRegistrar = Observation.ObservationRegistrar()
@Observable
还将所有存储的属性转换为计算属性,并生成带有下划线前缀的新存储属性。这就是解码失败的原因,因为 JSON 中没有下划线前缀的键。
我会手动实现
init(from:)
和 encode(to:)
。 Xcode 15 实际上会在自动完成过程中为您生成这些内容。添加 @Observable
之前,请开始在班级中输入 init
和 encode
,然后选择末尾带有 { ... }
的选项。
所有 3 个类的完整代码:
class Programmers: Codable, Identifiable {
var items: [Programmer] = [
Programmer(id: 1, name: "Programmer1", address: Address(street: "Street1", city: "City1"), changed: false),
Programmer(id: 2, name: "Programmer2", address: Address(street: "Street2", city: "City2"), changed: false),
]
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.items = try container.decode([Programmer].self, forKey: .items)
}
enum CodingKeys: CodingKey {
case items
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.items, forKey: .items)
}
}
@Observable
class Programmer: Codable, Identifiable {
var id: Int
var name: String
var address: Address
var changed: Bool
init(id: Int, name: String, address: Address, changed: Bool) {
self.id = id
self.name = name
self.address = address
self.changed = changed
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.name = try container.decode(String.self, forKey: .name)
self.address = try container.decode(Address.self, forKey: .address)
self.changed = try container.decode(Bool.self, forKey: .changed)
}
enum CodingKeys: CodingKey {
case id
case name
case address
case changed
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: .id)
try container.encode(self.name, forKey: .name)
try container.encode(self.address, forKey: .address)
try container.encode(self.changed, forKey: .changed)
}
}
@Observable
class Address: Codable, Identifiable {
var street: String
var city: String
init(street: String, city: String) {
self.street = street
self.city = city
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.street = try container.decode(String.self, forKey: .street)
self.city = try container.decode(String.self, forKey: .city)
}
enum CodingKeys: CodingKey {
case street
case city
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.street, forKey: .street)
try container.encode(self.city, forKey: .city)
}
}
也就是说,我不明白为什么你需要它们作为类。这些类看起来代表了应用程序的简单数据,并且完全可以是结构。您应该尝试在 SwiftUI 中尽可能多地使用结构体。 SwiftUI 可以检测结构中的变化,而不需要任何额外的东西,比如
@Observable
。唯一需要类的地方是当你需要它持久地保留在内存中时,例如其他班级的代表,例如 CLLocationManager
。