iOS定時器

小星星_ios發表於2020-03-27

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執行緒不安全
  • 狀態未判斷,如果多次呼叫會發生閃退

相關文章