要求
- Swift 5.0+
说明
我们开发时候经常会遇到使用定时器的场景,而直接使用系统默认的Timer又比较麻烦;
虽然有其他方案可以解决使用系统Timer的各种问题,不过对于同时有多个定时器,且需要统一时间的场景,也是比较麻烦。
所以YYGlobalTimer应运而生。。。
特点
- 内部使用系统Timer,在一个单独的子线程上运行,没有任务时,Timer不执行
- 不会强引用target
- 当target释放时,上面的所有任务会被自动清除
- 闭包方式使用
- 添加和移除操作在子线程,加了锁
- 可以选择让任务运行在各种队列,默认main
添加任务Api如下:
typealias Handler = (_ currentDate: Date) -> Void
/// 添加任务,添加一个任务后timer会自动开始
///
/// - parameter target: 任务的目标对象,不指定就是YYGlobalTimer.shared,target销毁后,任务自动清除
/// - parameter key: 任务的名字
/// - parameter interval: 任务执行的间隔,单位秒,最小粒度是0.05,注意,比如0.0533会被修正为0.05
/// - parameter queue: 任务执行的队列
/// - parameter action: 具体任务
class func addTask(on target: AnyObject = YYGlobalTimer.shared,
forKey key: String,
interval: TimeInterval,
queue: DispatchQueue = .main,
action: @escaping Handler)
使用Demo
简单使用场景如下:
注意task1,添加任务时没有指定target,需要手动移除
task2和task3在ViewController释放后会自动被移除
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
YYGlobalTimer.addTask(forKey: "task1", interval: 0.1111) { currentDate in
print("task1 date:\(currentDate) thread\(Thread.current)")
}
YYGlobalTimer.addTask(on: self, forKey: "task2", interval: 0.323, queue: .global()) { currentDate in
print("task2 date:\(currentDate) thread\(Thread.current)")
}
YYGlobalTimer.addTask(on: self, forKey: "task3", interval: 1.999) { currentDate in
print("task3 date:\(currentDate) thread\(Thread.current)")
}
}
deinit {
/// 添加时没有指定target的,需要手动移除
YYGlobalTimer.removeTasks(forKey: "task1")
}
}
完整实现代码如下:
public final class YYGlobalTimer {
public static let shared = YYGlobalTimer()
public var isRunning: Bool { timer.fireDate == .distantFuture }
/// 任务容器
private typealias TaskDict = [String: Task]
private var targetTasksDict = [String: TaskDict]()
/// 线程
private lazy var thread = Thread(target: self,
selector: #selector(threadTask),
object: nil)
private lazy var semaphore = DispatchSemaphore(value: 1)
/// 内部timer
private lazy var timer = Timer(fireAt: .distantFuture,
interval: interval,
target: self,
selector: #selector(timerTask),
userInfo: nil,
repeats: true)
/// timer每次开始运行的总时间,毫秒,每次start都会清0
private var duration: Millisecond = 0
/// 内部定时器间隔时间,默认0.05秒
private let interval = 0.05
private init() {
addTimerThread()
}
}
// MARK: - Public
public extension YYGlobalTimer {
typealias Handler = (_ currentDate: Date) -> Void
/// 添加任务,添加一个任务后timer会自动开始
///
/// - parameter target: 任务的目标对象,不指定就是YYGlobalTimer.shared,target销毁后,任务自动清除
/// - parameter key: 任务的名字
/// - parameter interval: 任务执行的间隔,单位秒,最小粒度是0.05,注意,比如0.0533会被修正为0.05
/// - parameter queue: 任务执行的队列
/// - parameter action: 具体任务
class func addTask(on target: AnyObject = YYGlobalTimer.shared,
forKey key: String,
interval: TimeInterval,
queue: DispatchQueue = .main,
action: @escaping Handler) {
shared.addTask(on: target,
forKey: key,
interval: interval,
queue: queue,
action: action)
}
/// 判断target对象上是否有key任务
class func hasTask(on target: AnyObject? = nil,
forKey key: String) -> Bool {
shared.hasTask(on: target, forKey: key)
}
/// 移除任务,没有任务的话timer会自动停止
class func removeTask(on target: AnyObject? = nil,
forKey key: String? = nil) {
shared.removeTask(on: target, forKey: key)
}
/// 暂停任务
class func pauseTask(on target: AnyObject? = nil,
forKey key: String) {
shared.pauseTask(on: target, forKey: key)
}
/// 恢复任务
class func resumeTask(on target: AnyObject? = nil,
forKey key: String) {
shared.resumeTask(on: target, forKey: key)
}
/// 移除所有任务,危险操作
class func removeAllTasks() {
shared.removeAllTask()
}
}
// MARK: - Thread & Timer
private extension YYGlobalTimer {
func addTimerThread() {
thread.start()
}
@objc func threadTask() {
autoreleasepool {
thread.name = "YYGlobalTimerThread"
RunLoop.current.add(timer, forMode: .common)
RunLoop.current.run()
}
}
@objc func timerTask() {
let currentDate = Date()
var hasTask = false
semaphore.wait()
targetTasksDict.forEach { _, targetDict in
targetDict.forEach { _, task in
/// 目标对象释放掉,删除target上的所有任务
if task.target == nil {
targetTasksDict.removeValue(forKey: task.targetName)
} else {
/// 只有duration是任务执行间隔时间的倍数时,才执行该任务
if duration > 0,
!task.isPaused,
duration % task.interval == 0 {
let execute = {
if task.target != nil {
task.task(currentDate)
}
}
task.queue.async(execute: execute)
}
hasTask = true
}
}
}
duration += UInt(interval * 1000)
/// 如果没有任务,暂停timer
if !hasTask {
pause()
}
semaphore.signal()
}
}
// MARK: - Add & Remove Task
private extension YYGlobalTimer {
typealias Millisecond = UInt
class Task {
weak var target: AnyObject?
var isPaused = false
var targetName: String
var task: Handler
var taskName: String
var queue: DispatchQueue
var interval: Millisecond // 任务的执行间隔,毫秒
init(target: AnyObject,
targetName: String,
task: @escaping Handler,
taskName: String,
queue: DispatchQueue,
interval: Millisecond) {
self.target = target
self.targetName = targetName
self.task = task
self.taskName = taskName
self.queue = queue
self.interval = interval
}
}
func addTask(on target: AnyObject,
forKey key: String,
interval: TimeInterval,
queue: DispatchQueue = .main,
action: @escaping Handler) {
let target = target
let targetKey = _targetKey(for: target)
/// 转换成对应的毫秒
let intervalMS = UInt((floor(interval * 10.0) / 10) * 1000)
let task = Task(target: target,
targetName: targetKey,
task: action,
taskName: key,
queue: queue,
interval: intervalMS)
addTask(task)
}
func addTask(_ task: Task) {
semaphore.wait()
/// 值类型,注意
if targetTasksDict[task.targetName] != nil {
_ = self.targetTasksDict[task.targetName]?.updateValue(task, forKey: task.taskName)
} else {
let targetTasksDict = [task.taskName: task]
self.targetTasksDict[task.targetName] = targetTasksDict
}
startIfNeeded()
semaphore.signal()
}
func removeTask(on target: AnyObject? = nil, forKey key: String? = nil) {
if target == nil, key == nil {
return
}
let targetKey = _targetKey(for: target)
semaphore.wait()
if let taskKey = key {
/// 删除target上指定任务
_ = targetTasksDict[targetKey]?.removeValue(forKey: taskKey)
} else {
/// 删除target上的所有任务
targetTasksDict.removeValue(forKey: targetKey)
}
pauseIfNeeded()
semaphore.signal()
}
func removeAllTask() {
semaphore.wait()
targetTasksDict.removeAll()
pause()
semaphore.signal()
}
func task(on target: AnyObject?, forKey key: String) -> Task? {
var task: Task?
let targetKey = _targetKey(for: target)
semaphore.wait()
task = targetTasksDict[targetKey]?[key]
semaphore.signal()
return task
}
func hasTask(on target: AnyObject?, forKey key: String) -> Bool {
return task(on: target, forKey: key) != nil
}
/// 暂停任务
func pauseTask(on target: AnyObject?,
forKey key: String) {
task(on: target, forKey: key)?.isPaused = true
}
/// 恢复任务
func resumeTask(on target: AnyObject?,
forKey key: String) {
task(on: target, forKey: key)?.isPaused = false
}
func _targetKey(for target: AnyObject?) -> String {
return "\(target ?? self)"
}
}
// MARK: - Start & Pause
private extension YYGlobalTimer {
func startIfNeeded() {
if !isRunning, targetTasksDict.values.count > 0 {
start()
}
}
func pauseIfNeeded() {
if isRunning, targetTasksDict.values.count == 0 {
pause()
}
}
func start() {
timer.fireDate = .init()
duration = 0
}
func pause() {
timer.fireDate = .distantFuture
}
}