You need to enable JavaScript to run this app. iOS保存NSTimer到UserDefaults触发NSInvalidArgumentException崩溃如何解决
优惠活动
大模型
产品
解决方案
定价
更多

iOS保存NSTimer到UserDefaults触发NSInvalidArgumentException崩溃如何解决

问题原因
  1. 首先Timer(即NSTimer)本身不遵守NSCoding协议,没有实现encodeWithCoder:方法,你直接对Timer对象执行归档操作时,系统会调用这个不存在的方法,直接抛出异常导致崩溃,和你看到的报错信息完全匹配。
  2. 即使你能强行绕过归档报错,Timer本身是和当前RunLoop绑定的运行时对象,它的触发逻辑、绑定的target、执行上下文都依赖当前App的运行环境,持久化存储后反归档得到的Timer也无法正常执行原有定时任务,本身就不属于可以持久化存储的对象类型。
解决方案

不要直接存储Timer实例,改为存储Timer的可序列化配置参数,需要使用时再根据配置重新创建Timer即可,具体步骤如下:

  • 封装自定义的Timer配置模型,遵守NSSecureCoding协议,存储定时间隔、触发时间、是否重复、关联业务标识、自定义参数等可序列化的属性
  • 将配置模型归档后存入UserDefaults,需要恢复Timer时先读取配置模型,再根据参数重新创建Timer实例,加入当前RunLoop即可正常运行
  • 额外注意:如果Timer需要在子线程运行,需要手动启动子线程的RunLoop,否则Timer不会触发,常规场景建议直接在主线程创建Timer避免调度问题

示例代码

1. 自定义Timer配置模型

class TimerConfig: NSObject, NSSecureCoding {
    static var supportsSecureCoding: Bool = true
    
    // 存储Timer的核心可序列化参数
    let interval: TimeInterval
    let fireDate: Date
    let repeats: Bool
    let businessKey: String // 关联的业务标识,用来绑定对应的触发方法
    let userInfo: [String: AnyHashable]? // 自定义参数
    
    init(interval: TimeInterval, fireDate: Date, repeats: Bool, businessKey: String, userInfo: [String: AnyHashable]?) {
        self.interval = interval
        self.fireDate = fireDate
        self.repeats = repeats
        self.businessKey = businessKey
        self.userInfo = userInfo
    }
    
    func encode(with coder: NSCoder) {
        coder.encode(interval, forKey: "interval")
        coder.encode(fireDate, forKey: "fireDate")
        coder.encode(repeats, forKey: "repeats")
        coder.encode(businessKey, forKey: "businessKey")
        coder.encode(userInfo, forKey: "userInfo")
    }
    
    required init?(coder: NSCoder) {
        interval = coder.decodeDouble(forKey: "interval")
        guard let fireDate = coder.decodeObject(forKey: "fireDate") as? Date,
              let businessKey = coder.decodeObject(forKey: "businessKey") as? String else {
            return nil
        }
        self.fireDate = fireDate
        self.repeats = coder.decodeBool(forKey: "repeats")
        self.businessKey = businessKey
        self.userInfo = coder.decodeObject(forKey: "userInfo") as? [String: AnyHashable]
    }
}

2. 配置存储读写逻辑

static var timerConfigDict : [String:TimerConfig]? {
    get{
        guard let outData = UserDefaults.standard.data(forKey: "timerConfigDict") else {
            return [:]
        }
        do {
            let dict = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(outData) as? [String:TimerConfig]
            return dict ?? [:]
        } catch {
            print("解档失败: \(error)")
            return [:]
        }
    }
    set{
        do {
            let data = try NSKeyedArchiver.archivedData(withRootObject: newValue ?? [:], requiringSecureCoding: true)
            UserDefaults.standard.set(data, forKey: "timerConfigDict")
        } catch {
            print("归档失败: \(error)")
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.15 14:24:03