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

如何用UserDefaults保存包含自定义对象的自定义对象?

别担心,这种嵌套自定义对象的存储其实就是单个自定义对象存储的延伸——只要让你嵌套的StatsIntervalModel也遵守NSCoding协议,实现对应的编码解码逻辑,外层的ConfigLabelMainList就能正常保存到UserDefaults里。我给你一步步拆解实现:

第一步:让嵌套类StatsIntervalModel遵守NSCoding

首先必须确保你的StatsIntervalModel继承NSObject并实现NSCoding的两个核心方法:encode(with:)(编码)和required init?(coder:)(解码)。假设这个类有几个示例属性,你可以参考下面的写法调整成自己的属性:

class StatsIntervalModel: NSObject, NSCoding {
    // 示例属性,替换成你自己的属性
    var interval: TimeInterval
    var displayName: String
    
    // 自定义初始化器
    init(interval: TimeInterval, displayName: String) {
        self.interval = interval
        self.displayName = displayName
    }
    
    // 编码方法:把属性写入NSCoder
    func encode(with coder: NSCoder) {
        coder.encode(interval, forKey: "interval")
        coder.encode(displayName, forKey: "displayName")
    }
    
    // 解码构造器:从NSCoder读取属性,注意安全的类型转换
    required init?(coder: NSCoder) {
        guard let name = coder.decodeObject(forKey: "displayName") as? String else {
            return nil // 读取失败则返回nil
        }
        self.interval = coder.decodeDouble(forKey: "interval")
        self.displayName = name
    }
}
第二步:完善外层类ConfigLabelMainList的NSCoding实现

接下来回到你的ConfigLabelMainList类,在它的编码和解码方法里,直接对每个StatsIntervalModel属性调用编码/解码方法即可——因为我们已经让内层类支持NSCoding了,系统会自动递归处理:

class ConfigLabelMainList: NSObject, NSCoding {
    var labelMiddleFirst: StatsIntervalModel
    var labelMiddleSecond: StatsIntervalModel
    var labelMiddleThird: StatsIntervalModel
    var labelRightFirst: StatsIntervalModel
    var labelRightSecond: StatsIntervalModel
    
    // 你的自定义初始化器
    init(labelMiddleFirst: StatsIntervalModel,
         labelMiddleSecond: StatsIntervalModel,
         labelMiddleThird: StatsIntervalModel,
         labelRightFirst: StatsIntervalModel,
         labelRightSecond: StatsIntervalModel) {
        self.labelMiddleFirst = labelMiddleFirst
        self.labelMiddleSecond = labelMiddleSecond
        self.labelMiddleThird = labelMiddleThird
        self.labelRightFirst = labelRightFirst
        self.labelRightSecond = labelRightSecond
    }
    
    // 编码方法:逐个编码嵌套的自定义对象
    func encode(with coder: NSCoder) {
        coder.encode(labelMiddleFirst, forKey: "labelMiddleFirst")
        coder.encode(labelMiddleSecond, forKey: "labelMiddleSecond")
        coder.encode(labelMiddleThird, forKey: "labelMiddleThird")
        coder.encode(labelRightFirst, forKey: "labelRightFirst")
        coder.encode(labelRightSecond, forKey: "labelRightSecond")
    }
    
    // 解码构造器:逐个解码嵌套对象,确保类型转换安全
    required init?(coder: NSCoder) {
        guard let middleFirst = coder.decodeObject(forKey: "labelMiddleFirst") as? StatsIntervalModel,
              let middleSecond = coder.decodeObject(forKey: "labelMiddleSecond") as? StatsIntervalModel,
              let middleThird = coder.decodeObject(forKey: "labelMiddleThird") as? StatsIntervalModel,
              let rightFirst = coder.decodeObject(forKey: "labelRightFirst") as? StatsIntervalModel,
              let rightSecond = coder.decodeObject(forKey: "labelRightSecond") as? StatsIntervalModel else {
            return nil
        }
        
        self.labelMiddleFirst = middleFirst
        self.labelMiddleSecond = middleSecond
        self.labelMiddleThird = middleThird
        self.labelRightFirst = rightFirst
        self.labelRightSecond = rightSecond
    }
}
第三步:保存和读取到UserDefaults

最后就是把ConfigLabelMainList实例归档成Data,再存入UserDefaults,读取时反向操作即可:

// 保存配置到UserDefaults
func saveConfigToUserDefaults() {
    // 先创建测试用的StatsIntervalModel实例
    let interval1 = StatsIntervalModel(interval: 3600, displayName: "1小时")
    let interval2 = StatsIntervalModel(interval: 86400, displayName: "1天")
    let interval3 = StatsIntervalModel(interval: 604800, displayName: "1周")
    let interval4 = StatsIntervalModel(interval: 2592000, displayName: "1个月")
    let interval5 = StatsIntervalModel(interval: 31536000, displayName: "1年")
    
    let config = ConfigLabelMainList(labelMiddleFirst: interval1,
                                     labelMiddleSecond: interval2,
                                     labelMiddleThird: interval3,
                                     labelRightFirst: interval4,
                                     labelRightSecond: interval5)
    
    // 归档成Data
    guard let configData = try? NSKeyedArchiver.archivedData(withRootObject: config, requiringSecureCoding: false) else {
        print("归档失败")
        return
    }
    UserDefaults.standard.set(configData, forKey: "AppConfigLabelMainList")
}

// 从UserDefaults读取配置
func loadConfigFromUserDefaults() -> ConfigLabelMainList? {
    guard let configData = UserDefaults.standard.data(forKey: "AppConfigLabelMainList") else {
        return nil
    }
    return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(configData) as? ConfigLabelMainList
}
关键注意点
  • 只要是嵌套的自定义对象,必须全部遵守NSCoding协议,不管嵌套多少层,系统都会递归处理编码和解码。
  • 解码时一定要做安全的类型转换,用guard或if let避免因为类型不匹配导致崩溃。
  • 如果你的属性是可选类型,在解码时要对应处理(比如用decodeObject(forKey:)直接赋值给可选属性)。

内容的提问来源于stack exchange,提问作者PaFi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 08:28:50