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