You need to enable JavaScript to run this app. Swift类扩展中如何正确失效关联Timer解决循环引用问题
优惠活动
大模型
产品
解决方案
定价
更多

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清理逻辑

可落地的实现方案

  1. 修正关联对象的基础逻辑
    关联对象的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)
        }
    }
    
  2. 从根源切断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)
        }
    }
    
  3. Timer失效逻辑的兜底实现
    使用弱引用方案后不存在循环引用,类实例可以正常被系统回收:
    • 对于一次性非重复触发的Timer,触发完成后会自动失效,类实例回收时持有的Timer也会随之释放,不需要额外写清理逻辑
    • 如果是需要重复触发的Timer,在闭包中检测到类实例已经被释放时,主动调用invalidate()停止定时器,避免无效的CPU占用

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.03 10:24:01