重学GCD之 DispatchSourceTimer

1,118 阅读2分钟

重学GCD之 DispatchSourceTimer

iOS 中上层的定时器API以及它们的问题

通常来说, 我们在使用iOS 的Timer 或者 GCD 的 DispatchAfter的API, 他们都会有或多或少的问题.

  1. Timer的生命周期管理问题, Timer的事件触发依赖Runloop
  1. DispatchAfter会在指定时间以后才加入到DispatchQueue中. 他们的定时准确度都比较差
  2. OCperformxxx API本质底层是一个 Timer

这里就不展开了, 网上有很多文章都有说明.

更好的Timer - DispatchSourceTimer

GCD中DispatchSource 相关的API是iOS针对kqueue事件源的封装. kqueue作为Apple内核支持的事件驱动方式, 具体的api和功能与epoll类似. 我们可以借助DispatchSource来实现精度更高的定时器, 这里我们称为GCD Timer

通过系统的API. 我们知道GCD Timer支持的功能非常棒, 它更优秀体现在如下:

  1. 支持延迟启动, 支持Repeat, 支持 leeyway 触发精度
  2. 支持 Suspend, Resume
  3. 支持第一次触发的 notify
  4. 支持指定event触发的 dispatchQueue
  5. 支持cancel

但是, 它也有一些问题!!! 具体来说包括如下问题:

  1. suspend/resume 必须成对出现, 否则GCD Timer内部状态紊乱非常容易Crash
  2. suspend/resume状态下调用timer.cancel 也会导致crash
  3. 生命周期管理需要注意!!!

GCD Timer的封装

因此, 这里针对以上的问题, 对GCD Timer 进行了封装, 注意resume/suspend方法必须在同一个线程中调用:

//
//  RepeatingTimer.swift
//  LearnSwiftDispatchItem
//
//  Created by brownfeng on 2022/4/27.
//import Foundation
​
// 实现的原因是:
// 1. 需要 suspend/resume
// 2. 不同状态下 timer = nil 会 crash!!!
class RepeatingTimer {
    
    var registrationHandler: (() -> Void)?
    var eventHandler: (() -> Void)?
    var workQueue: DispatchQueue?
​
    init(timeInterval: TimeInterval) {
        self.timeInterval = timeInterval
    }
    
    // We should also ensure its accessed from the same thread/queue
    // 防止 data race
    public func resume() {
        if state == .resumed {
            return
        }
        state = .resumed
        timer.resume()
    }
​
    public func suspend() {
        if state == .suspended {
            return
        }
        state = .suspended
        timer.suspend()
    }
    
    // MARK: - Private Properties
    
    private let timeInterval: TimeInterval
​
    private lazy var timer: DispatchSourceTimer = {
        let t = DispatchSource.makeTimerSource(flags: [], queue: workQueue)
        t.schedule(deadline: .now() + self.timeInterval, repeating: self.timeInterval)
        t.setEventHandler(handler: { [weak self] in
            self?.eventHandler?()
        })
        t.setRegistrationHandler {  [weak self] in
            self?.registrationHandler?()
        }
        return t
    }()
​
    private enum State {
        case suspended
        case resumed
    }
    private var state: State = .suspended
   
    deinit {
        timer.setEventHandler {}
        timer.cancel()
       /*
        If the timer is suspended, calling cancel without resuming
        triggers a crash. This is documented here
        https://forums.developer.apple.com/thread/15902
        */
        resume()
        eventHandler = nil
    }
}