You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 21:07:36