1.背景介绍 在iOS app经常会用到倒计时功能。普通的Timer 会受到系统事件干扰,而且容易造成循环引用。
本示例采用DispatchSourceTimer + 中介者消息转发机制 来解决上述的两个问题 。WRGCDTimerWrapper 封装了DispatchSourceTimer
tip: 由于DispatchSourceTimer 在suspend 状态下设置为nil 会导致crash,且api 并未提供查询是否正在suspend的方法,所以需要自己记录。 * github address: github.com/gree180160/…
有图有真相:
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 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
}
}