美文网首页
Timer的循环引用问题

Timer的循环引用问题

作者: sasky2008 | 来源:发表于2019-03-26 18:30 被阅读0次

平时使用Timer/NSTimer时候, 由于Timer会持有self, 所以释放一直是一个麻烦.
本文不再对于Timer初始化, 需要加入Runloop等事项进行讨论了, 使用scheduledTimer默认加入Runloop.

先来段看起来正确, 却无法释放的错误写法

class TimerViewController: UIViewController {
    lazy var timer: Timer = {
        return Timer.scheduledTimer(timeInterval: 1, 
                                    target: self, 
                                    selector:  #selector(timeRun), 
                                    userInfo: nil, 
                                    repeats: true)
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        timer.fire()
    }

   @objc func timeRun() {
       print(#function)
    }

   deinit() {
      /// WARNING: 此处不可能执行了, 因为timer持有了self
      /// 手动去释放 timer.invalidate()的话, 比如remove或者点击按钮等, 也不是很好
      timer.invalidate()
   }
}

解决思路就是打破这个引用, 也就是Timer不再持有self.
让Timer随着self生命周期一起走, 尽量保证原始API

先来一个效果代码, 注意观察Timer的初始化.safe_scheduled

class TimerViewController: UIViewController {
    lazy var timer: Timer = {
       return Timer.safe_scheduled(interval: 1, 
                                   target: self, 
                                   selector: #selector(timeGo), 
                                   repeats: true, 
                                   userInfo: nil)
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        timer.fire()
    }

   @objc func timeRun() {
       print( #function)
    }
    
    deinit() {
       print( #function)
   }
}

具体实现细节

extension Timer {
  /// 此Timer不可在deinit/dealloc中执行invalidate等任何操作, 已经随着target生命周期释放了
  static func safe_scheduled(interval: TimeInterval, 
                             target: Any, 
                             selector: Selector, 
                             repeats: Bool, 
                             userInfo: Any? = nil) -> Timer {
    return WolfTimer(scheuled: interval,
                     target: target,
                     selector: selector,
                     userInfo: userInfo,
                     repeats: repeats).k_timer ??
      Timer.scheduledTimer(timeInterval: interval,
                           target: target,
                           selector: selector,
                           userInfo: userInfo,
                           repeats: repeats)
  }
}

class WolfTimer {
  
  convenience init(scheuled interval: TimeInterval, 
                   target: Any, 
                   selector: Selector, 
                   userInfo: Any?, repeats: Bool) {
    self.init()
    k_target = target as? NSObjectProtocol
    k_selector = selector
    k_timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(timer_run), userInfo: userInfo, repeats: repeats)
  }
  
  weak var k_timer: Timer?
  weak var k_target: NSObjectProtocol?
  var k_selector: Selector?
  
  @objc func timer_run() {
    guard let target = k_target, 
          let selector = k_selector,
          target.responds(to: selector) else { 
       k_timer?.invalidate()
       return
    }
   target.perform(selector, with: k_timer)
  }
  
  deinit {
    print(#function + "WolfTimer")
  }
  
}

也就是说, ViewController中的Timer被vc所retain持有,
Timer持有WolfTimer的对象.
Timer执行计时器, 则由WolfTimer根据target和selector去perform执行.
如果ViewController被释放, 则由于

weak var k_target: NSObjectProtocol?
weak var k_timer: Timer?

是weak的弱引用, 而也会变为nil, 也就释放了.

mark注意点: 原本觉得k_timer可以不为weak, 但是考虑到timer可能并没有执行, 那么WolfTimer也就无法释放了, 我就试了试, 结果发现另外一个坑, 如果k_timer不使用weak, 那么如果ViewController的deinit中如果执行timer.invalidate()或者其他方法, 会触发VC过度释放.

从大神处学到了一种新的做法.
感谢 LEE大神
Timer初始化时候, 使用WeakObjc(self)

class TimerViewController: UIViewController {
    lazy var timer: Timer = {
        return Timer.scheduledTimer(timeInterval: 1, 
                                    target: WeakObjc(self), 
                                    selector:  #selector(timeRun), 
                                    userInfo: nil, 
                                    repeats: true)
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        timer.fire()
    }

   @objc func timeRun() {
       print(#function)
    }
}


class WeakObjc: NSObject {
    
    private weak var target: AnyObject?
    
    init(_ target: AnyObject) {
        self.target = target
        super.init()
    }
    
    override func forwardingTarget(for aSelector: Selector!) -> Any? {
        return target
    }
    
    override func responds(to aSelector: Selector!) -> Bool {
        return target?.responds(to: aSelector) ?? super.responds(to: aSelector)
    }
    
    override func method(for aSelector: Selector!) -> IMP! {
        return target?.method(for: aSelector) ?? super.method(for: aSelector)
    }
    
    override func isEqual(_ object: Any?) -> Bool {
        return target?.isEqual(object) ?? super.isEqual(object)
    }
    
    override func isKind(of aClass: AnyClass) -> Bool {
        return target?.isKind(of: aClass) ?? super.isKind(of: aClass)
    }
    
    override var superclass: AnyClass? {
        return target?.superclass
    }
    
    override func isProxy() -> Bool {
        return target?.isProxy() ?? super.isProxy()
    }
    
    override var hash: Int {
        return target?.hash ?? super.hash
    }
    
    override var description: String {
        return target?.description ?? super.description
    }
    
    override var debugDescription: String {
        return target?.debugDescription ?? super.debugDescription
    }
    
    deinit { print("deinit:\t\(classForCoder)") }
}

感谢 LEE大神

相关文章

网友评论

      本文标题:Timer的循环引用问题

      本文链接:https://www.haomeiwen.com/subject/ykynvqtx.html