iOS保存NSTimer到UserDefaults触发NSInvalidArgumentException崩溃如何解决
问题原因
- 首先
Timer(即NSTimer)本身不遵守NSCoding协议,没有实现encodeWithCoder:方法,你直接对Timer对象执行归档操作时,系统会调用这个不存在的方法,直接抛出异常导致崩溃,和你看到的报错信息完全匹配。 - 即使你能强行绕过归档报错,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
相关产品推荐
相关产品推荐

