在 AppStorage 中设置自定义 Struct 值时应用程序崩溃

Kex*_*Kex 4 ios swift swiftui appstorage

我有一个想要存储在的自定义结构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
    }

}
Run Code Online (Sandbox Code Playgroud)

那么我有以下观点:

struct DemoView: View {

    @AppStorage(UserDefaults.StorageKeys.budget.rawValue) var budget = Budget()

    var body: some View {
        Button("Update") {
            budget.total = 10
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当我点击按钮时,应用程序崩溃并显示Thread 1: EXC_BAD_ACCESSon guard let data = try? JSONEncoder().encode(self)for rawValuein Budget。我在这里做错了什么?

Swe*_*per 6

您正在遇到无限递归。这是因为符合两者的类型EncodableRawRepresentable自动获取encode(to:)实现(source),该实现对原始值进行编码。这意味着当你调用 时JSONEncoder().encode,它会尝试调用 的 getter rawValue,从而调用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)
}
Run Code Online (Sandbox Code Playgroud)

请注意,您还应该init(from:)显式实现,因为您还获得了一个尝试将 JSON 解码为单个 JSON 字符串的init(from:)实现(source),而您当然不希望这样做。

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