我有一个自定义结构要存储在AppStorage
:中
struct Budget: Codable, RawRepresentable {
enum CodingKeys: String, CodingKey {
case total, spent
}
var total: Double
var spent: Double
init(total: Double = 5000.0, spent: Double = 3000.0) {
self.total = total
self.spent = spent
}
init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode(Budget.self, from: data)
else { return nil }
self = result
}
var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return ""
}
return result
}
}
然后我有以下观点:
struct DemoView: View {
@AppStorage(UserDefaults.StorageKeys.budget.rawValue) var budget = Budget()
var body: some View {
Button("Update") {
budget.total = 10
}
}
}
当我点击按钮时,应用程序在Budget
中的rawValue
与guard let data = try? JSONEncoder().encode(self)
上的Thread 1: EXC_BAD_ACCESS
崩溃。我在这里做错了什么?
您正在运行无限递归。这是因为同时符合Encodable
和RawRepresentable
的类型会自动获得该encode(to:)
实现(源(,该实现对原始值进行编码。这意味着当您调用JSONEncoder().encode
时,它将尝试调用rawValue
的getter,后者调用JSONEncoder().encode
,从而形成无限递归。
为了解决这个问题,您可以显式地实现encode(to:)
:
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(total, forKey: .total)
try container.encode(spent, forKey: .spent)
}
请注意,您还应该显式地实现init(from:)
,因为您还获得了一个init(from:)
实现(源代码(,该实现试图将JSON解码为单个JSON字符串,而您肯定不希望这样做。
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
total = try container.decode(Double.self, forKey: .total)
spent = try container.decode(Double.self, forKey: .spent)
}