Swift中如何让UserDefaults解码时自动补全结构体缺失属性?
解决UserDefaults自定义结构体新增属性后旧数据解码失败的问题
这个问题我之前也碰到过——当给Codable结构体新增带默认值的属性后,旧的编码数据里没有这个字段,默认的Codable解码会直接抛出keyNotFound错误,因为它要求所有非可选属性必须存在对应的键值。下面是最直接有效的解决方案:
核心思路:自定义解码初始化方法
我们需要手动实现init(from decoder: Decoder),对新增的属性使用decodeIfPresent方法尝试解码,当字段缺失时自动填充默认值,而不是让解码过程直接失败。
完整代码示例
首先是更新后的Person结构体,包含自定义解码逻辑:
import Foundation struct Person: Codable { let firstName: String let lastName: String let birthday: Date let favorite: Bool // 新增的带默认值属性 // 定义编码解码用到的键 enum CodingKeys: String, CodingKey { case firstName, lastName, birthday, favorite } // 自定义解码初始化 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // 原有属性直接解码(旧数据中肯定存在这些字段) firstName = try container.decode(String.self, forKey: .firstName) lastName = try container.decode(String.self, forKey: .lastName) birthday = try container.decode(Date.self, forKey: .birthday) // 新增属性:尝试解码,缺失则用默认值false favorite = try container.decodeIfPresent(Bool.self, forKey: .favorite) ?? false } }
配合你的ObjectSavable协议使用
确保你的UserDefaults扩展中的解码逻辑正常调用这个自定义初始化方法就行,比如你的load方法可以保持不变(如果已经设置了正确的日期解码策略的话):
protocol ObjectSavable { func save<Object>(_ object: Object, forKey key: String) throws where Object: Encodable func load<Object>(forKey key: String, as type: Object.Type) throws -> Object where Object: Decodable } extension UserDefaults: ObjectSavable { func save<Object>(_ object: Object, forKey key: String) throws where Object: Encodable { let encoder = JSONEncoder() encoder.dateEncodingStrategy = .iso8601 // 和解码策略保持一致 let data = try encoder.encode(object) set(data, forKey: key) } func load<Object>(forKey key: String, as type: Object.Type) throws -> Object where Object: Decodable { guard let data = data(forKey: key) else { throw ObjectSavableError.noValue } let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 // 处理Date类型的解码 return try decoder.decode(type, from: data) } } enum ObjectSavableError: String, LocalizedError { case noValue = "未找到对应键的值" case unableToDecode = "无法从UserDefaults中解码对象" var errorDescription: String? { rawValue } }
为什么这个方法有效?
- 默认的Codable解码逻辑会严格检查所有非可选属性对应的键是否存在,不存在就抛出错误;
decodeIfPresent会尝试解码指定键的值,如果键不存在则返回nil,我们再用??运算符给它设置默认值false;- 原有属性依然用
decode方法,因为旧数据中肯定存在这些字段,不会有问题。
以后如果再给Person结构体新增其他带默认值的属性,都可以用同样的方式在init(from decoder:)中处理,保证新旧数据的兼容性。
内容的提问来源于stack exchange,提问作者אורי orihpt
相关产品推荐
相关产品推荐

