iOS定时器

1,096 阅读1分钟

CADisplayLink、NSTimer

  • 有循环引用问题
    • 如何解决?
1. 利用闭包,并弱引用其中的 self (建议使用这一种方式)
    timer = Timer(fire: Date.distantPast, interval: 1, repeats: true) { [weak self] (timer) in
        self?.test()
    }
    RunLoop.main.add(timer!, forMode: RunLoop.Mode.common)
    下面这个类方法和上面的是一样的,应该就是对上面的封装,默认已经把timer加进runloop中了
    Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] (timer) in
        self?.test()
    }
    
2. 利用Proxy(Swift无法使用了,使用replaceMethod替代)
class HXTimerProxy: NSObject {
    
    weak var target: NSObjectProtocol?
    weak var timer: Timer?
    var selector: Selector?
    
    public required init(target: NSObjectProtocol?, selector: Selector?) {
        self.target = target
        self.selector = selector
        
        super.init()
        
        guard let target = target, let selector = selector, target.responds(to: selector) else {
            return
        }
        
        let method = class_getInstanceMethod(self.classForCoder, #selector(HXTimerProxy.redirectionMethod))!
        class_replaceMethod(self.classForCoder, selector, method_getImplementation(method), method_getTypeEncoding(method))
    }
    
    @objc func redirectionMethod () {
        if let target = target {
            target.perform(selector)
        } else {
            timer?.invalidate()
        }
    }
}

然后在对应的地方调用即可
let proxy = HXTimerProxy(target: aTarget as? NSObjectProtocol, selector: aSelector)
        let timer = Timer(timeInterval: ti, target: proxy, selector: aSelector, userInfo: userInfo, repeats: yesOrNo)
        proxy.timer = timer
        RunLoop.main.add(timer, forMode: RunLoop.Mode.common)

  • 有不准确的问题
  • 必须保证在一个活跃的 runloop
    • 因为是基于Runloop的,所以每次循环执行完来的时间点是无法确定的,因为
  • CADisplayLink,CPU负载的时候会影响触发事件,且触发事件大于触发间隔会导致掉帧现象
let timer = Timer(timeInterval: 1, target: self, selector: #selector(test), userInfo: nil, repeats: true)

@objc private func test() {
        sleep(1)
        print(CACurrentMediaTime())
    }

执行的结果为

288462.109615096
288464.092074808
288466.092211654
288468.092896747
288470.093108874

也就是说他会 1s 的定时器会隔 2s 打印,这显然不符合要求。

利用GCD封装定时器

public class HXTimer {
    private let sourceTimer: DispatchSourceTimer
    
    public class func every(_ interval: DispatchTimeInterval, handle: @escaping () -> Void) -> HXTimer {
        let timer = HXTimer(interval: interval, repeats: true, handler: handle)
        timer.start()
        return timer
    }
    
    public init(interval: DispatchTimeInterval, deadline: DispatchTime = .now(), repeats: Bool = false, queue: DispatchQueue? = nil , handler: @escaping () -> Void) {
        sourceTimer = DispatchSource.makeTimerSource(queue: queue ?? DispatchQueue(label: "com.hxtimer.queue"))
        sourceTimer.schedule(deadline: deadline, repeating: repeats ? interval : .never, leeway: .milliseconds(10))
        sourceTimer.setEventHandler(handler: handler)
    }
    
    deinit {
        cancel()
    }
}

extension HXTimer {
    public func start() {
        sourceTimer.resume()
    }

    public func cancel()  {
        sourceTimer.setEventHandler(handler: nil)
        sourceTimer.cancel()
    }

}

Timer的Demo

  • Timer线程不安全
  • 状态未判断,如果多次调用会发生闪退