1. Background The countdown function is often used in iOS apps. Regular timers are disturbed by system events and are prone to circular references.

This example uses the DispatchSourceTimer + intermediary message forwarding mechanism to solve the above two problems. WRGCDTimerWrapper encapsulates the DispatchSourceTimer

Tip: If DispatchSourceTimer is set to nil in the suspend state, crash will occur, and the API does not provide a method to query whether or not you are suspending. * making address: github.com/gree180160/…

There is a picture and there is a truth:

The realization of the WRGCDTimerWrapper

import UIKit class WRGCDTimerWrapper: NSObject { weak var target: AnyObject! var timer: DispatchSourceTimer! ///resume + 1 , suspend - 1, kee deinit safe private var sourceCount: Int = 0 deinit { guard timer ! = nil else { return } timer.cancel() if sourceCount <= 0 { self.fireTimer() } timer = nil } init(target: AnyObject, timerAction: Selector) { super.init() self.target = target let queue = DispatchQueue.global() timer = DispatchSource.makeTimerSource(flags: [], queue: queue) timer.schedule(deadline: DispatchTime.now(), repeating: .seconds(1), leeway: .milliseconds(10)) timer.setEventHandler {[weak self] in guard let storngSelf = self else {return} Dispatchqueue.main.async {storngself. perform(timerAction)}} public func fireTimer() {guard sourceCount <= 0 else {return} timer.resume() sourceCount += 1} // Stop timer block temporay(With the use of resume(),  one by one) public func stopTimer() { guard sourceCount > 0 else { return } timer.suspend() sourceCount -= 1 } /// Destroy timer, used when you are never use it . public func destroyTimer() { timer.cancel() } /// send aselector to another target /// - Parameter aSelector: aselector /// - Returns: new receiver override func forwardingTarget(for aSelector: Selector) -> Any? { return target } }Copy the code

Invoke the sample

import Foundation class TestVC: BaseViewController { var timer: WRGCDTimerWrapper! var numberValue: Int = 0 var lab: UILabel! deinit { print("TestVC deinit") guard timer ! = nil else { return } timer.destroyTimer() } override func viewDidLoad() { super.viewDidLoad() self.title = "test" self.configureSubviews() } func configureSubviews() { lab = UILabel(frame: CGRect(x: 10, y: 200, width: self.view.frame.width - 20, height: 40)) lab.textAlignment = .center self.view.addSubview(lab) timer = WRGCDTimerWrapper(target: self, timerAction: #selector(timerAction)) timer.fireTimer() lab.text = "number is " + numberValue.description let button = UIButton(frame:  CGRect(x: 100, y: 360, width: 140, height: 70)) button.setTitle("stop", for: .normal) button.setTitle("fire", for: .selected) button.backgroundColor = .brown self.view.addSubview(button) button.addTarget(self, action: #selector(buttonAction(sender:)), for: .touchUpInside) } @objc func buttonAction(sender: UIButton) { if sender.isSelected { timer.fireTimer() }else { timer.stopTimer() } sender.isSelected = ! sender.isSelected } @objc func timerAction() { numberValue += 1 lab.text = "number is " + numberValue.description } }Copy the code