Swift Timer

1,613 阅读1分钟

1.背景介绍 在iOS app经常会用到倒计时功能。普通的Timer 会受到系统事件干扰,而且容易造成循环引用。

本示例采用DispatchSourceTimer + 中介者消息转发机制 来解决上述的两个问题 。WRGCDTimerWrapper 封装了DispatchSourceTimer

tip: 由于DispatchSourceTimer 在suspend 状态下设置为nil 会导致crash,且api 并未提供查询是否正在suspend的方法,所以需要自己记录。 * github address: github.com/gree180160/…

有图有真相:

RPReplay_Final1617867044.gif

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)
            }
        }
    }
    /// 启动timer
    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
    }
}

调用示例

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
    }
}