在AppStorage中设置自定义Struct值时应用崩溃,求排查解决
Budget结构体配合@AppStorage使用时崩溃问题分析
问题场景
定义了同时实现Codable和RawRepresentable的Budget结构体,用于@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。
崩溃原因
崩溃根源是无限递归调用:
当@AppStorage需要将Budget实例转换为rawValue存储时,会调用rawValue计算属性,而该属性内部通过JSONEncoder.encode(self)来序列化实例。但JSONEncoder在编码Budget实例时,触发的默认Codable合成逻辑会间接再次触发rawValue的访问,形成无限递归,最终耗尽栈空间引发内存访问错误。
解决方案
有两种可行的修复方案:
方案一:手动实现Codable的编码/解码逻辑
明确指定编码和解码仅操作total和spent存储属性,避免默认合成逻辑引发的递归:
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 } // RawRepresentable实现 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 } // 手动实现Codable编码,仅处理存储属性 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) } // 手动实现Codable解码,仅处理存储属性 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) } }
方案二:简化RawRepresentable实现,绕过Codable
直接通过字符串拼接/拆分实现RawRepresentable,完全避免使用JSONEncoder/JSONDecoder,从根源上消除递归可能:
struct Budget: Codable, RawRepresentable { var total: Double var spent: Double init(total: Double = 5000.0, spent: Double = 3000.0) { self.total = total self.spent = spent } init?(rawValue: String) { let components = rawValue.split(separator: ",") guard components.count == 2, let total = Double(components[0]), let spent = Double(components[1]) else { return nil } self.total = total self.spent = spent } var rawValue: String { return "\(total),\(spent)" } }
内容的提问来源于stack exchange,提问作者Kex
相关产品推荐
相关产品推荐

