Swift类扩展中如何正确失效关联Timer解决循环引用问题
类扩展中添加Timer遇到循环引用与deinit编译报错问题
提问内容
我尝试在类扩展中添加一个Timer属性,具体代码如下:
@objc public extension RCTTouchHandler { static let kSessionTimer = "sessionTimer" var sessionTimer: Timer { get { return objc_getAssociatedObject(self, RCTTouchHandler.kSessionTimer) as? Timer ?? Timer.scheduledTimer(timeInterval: RCTTouchHandler.kSessionExpiredDuration, target: self, selector: #selector(touchSessionExpiration), userInfo: nil, repeats: false) } set(timer) { objc_setAssociatedObject(self, RCTTouchHandler.kSessionTimer, self, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } @objc func touchSessionExpiration() { print("session expired") } deinit { self.sessionTimer.invalidate() } }
此时,sessionTimer对当前类实例持有强引用,同时类实例也对sessionTimer持有强引用,形成循环引用。为避免循环引用造成的内存泄漏,本应在类实例销毁时调用invalidate方法使定时器失效,但我在扩展的deinit方法中编写失效逻辑时遇到了编译报错:
Deinitializers may only be declared within a class
根据Swift官方文档说明:
扩展可以为类添加新的便利构造器,但无法为类添加新的指定构造器或反初始化器,这类成员必须由类的原始实现提供。
因此想咨询:在该类扩展场景下,应当采用什么方式正确实现定时器的失效逻辑。
参考解答
现存代码硬伤排查
- 关联对象setter逻辑错误:当前实现里
objc_setAssociatedObject传入的关联值是self,并非真正需要存储的Timer实例,会导致getter永远读取不到有效的Timer对象 - 强引用循环的核心根源:传统selector模式的
scheduledTimer初始化方法,会对传入的target参数持有强引用;再加上通过关联对象把Timer强持有在类实例上,形成双向强引用环,类实例根本无法走到释放流程,就算deinit逻辑能通过编译也不会执行 - 语法限制客观存在:Swift明确不允许在类扩展中实现deinit,无法通过扩展实现析构时的Timer清理逻辑
可落地的实现方案
- 修正关联对象的基础逻辑
关联对象的key优先使用静态变量的地址而非字符串,避免运行时的key冲突。setter方法的关联值修改为传入的Timer实例,不再错误存储self:// 关联对象key声明 private static var associatedTimerKey: Void? var sessionTimer: Timer { // 原有getter逻辑保留框架 set { objc_setAssociatedObject(self, &RCTTouchHandler.associatedTimerKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } - 从根源切断Timer对类实例的强引用
放弃使用selector模式的Timer初始化方法,改用闭包式的Timer API,在闭包中对类实例使用[weak self]弱引用修饰,彻底消除双向强引用环:var sessionTimer: Timer { get { // 先取已存在的Timer实例,避免重复创建 if let existingTimer = objc_getAssociatedObject(self, &RCTTouchHandler.associatedTimerKey) as? Timer { return existingTimer } // 用闭包初始化Timer,内部弱引用self let timer = Timer(timeInterval: RCTTouchHandler.kSessionExpiredDuration, repeats: false) { [weak self] timer in guard let self = self else { // 类实例已被释放,直接停止Timer避免资源空转 timer.invalidate() return } self.touchSessionExpiration() } // 把Timer加入主RunLoop,保证正常触发 RunLoop.main.add(timer, forMode: .common) // 存到关联对象里 objc_setAssociatedObject(self, &RCTTouchHandler.associatedTimerKey, timer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return timer } set { objc_setAssociatedObject(self, &RCTTouchHandler.associatedTimerKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } - Timer失效逻辑的兜底实现
使用弱引用方案后不存在循环引用,类实例可以正常被系统回收:- 对于一次性非重复触发的Timer,触发完成后会自动失效,类实例回收时持有的Timer也会随之释放,不需要额外写清理逻辑
- 如果是需要重复触发的Timer,在闭包中检测到类实例已经被释放时,主动调用
invalidate()停止定时器,避免无效的CPU占用
内容的提问来源于stack exchange,提问作者RY_ Zheng
相关产品推荐
相关产品推荐

