Last active
August 23, 2018 10:53
-
-
Save ZalyalovIldar/a02080141ded4f9de29bd94bc5e7444c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Timer that can work in background and update timer count if needed even if app resign active | |
class RepeatingTimer { | |
fileprivate enum State { | |
case suspended | |
case resumed | |
} | |
let timeInterval: TimeInterval | |
/// background queue handler | |
var eventHandlerOnBackground: (() -> Void)? | |
/// main queue handler | |
var eventHandlerOnMain: (() -> Void)? | |
/// main queue handler trigger when timer finish | |
var timerFinishEventHandler: (() -> Void)? | |
/// Timer count in seconds | |
var timerCount: Int? { | |
didSet{ | |
shouldContinueTimer = true | |
guard timerEventHandler == nil else { return } | |
timerEventHandler = { [weak self] in | |
guard let strongSelf = self else { return } | |
guard strongSelf.shouldContinueTimer else { | |
strongSelf.suspend() | |
return | |
} | |
if let count = strongSelf.timerCount, count <= 0 { | |
strongSelf.shouldContinueTimer = false | |
strongSelf.suspend() | |
DispatchQueue.main.async { | |
strongSelf.timerFinishEventHandler?() | |
} | |
} | |
else if let count = strongSelf.timerCount { | |
strongSelf.timerCount = count - Int(strongSelf.timeInterval) | |
} | |
} | |
} | |
} | |
fileprivate var notificationCenter: NotificationCenter = NotificationCenter.default | |
fileprivate var state: State = .suspended | |
fileprivate var resignActiveDate: Date = Date() | |
fileprivate var shouldContinueTimer: Bool = true | |
fileprivate var timerEventHandler: (() -> Void)? | |
fileprivate lazy var timer: DispatchSourceTimer = { | |
let timer = DispatchSource.makeTimerSource() | |
timer.schedule(deadline: .now() + self.timeInterval, repeating: self.timeInterval) | |
timer.setEventHandler(handler: { [weak self] in | |
self?.timerEventHandler?() | |
self?.eventHandlerOnBackground?() | |
DispatchQueue.main.async { [weak self] in | |
self?.eventHandlerOnMain?() | |
} | |
}) | |
return timer | |
}() | |
init(timeInterval: TimeInterval) { | |
self.timeInterval = timeInterval | |
subscribeToAppNotifications() | |
} | |
convenience init (timeInterval: TimeInterval, timerCount: Int) { | |
self.init(timeInterval: timeInterval) | |
self.timerCount = timerCount | |
} | |
deinit { | |
timer.setEventHandler {} | |
timer.cancel() | |
// Can be crash if not resume after cancel while timer was suspended: https://forums.developer.apple.com/thread/15902 | |
resume() | |
eventHandlerOnBackground = nil | |
eventHandlerOnMain = nil | |
timerFinishEventHandler = nil | |
timerEventHandler = nil | |
notificationCenter.removeObserver(self) | |
} | |
//MARK: Open methods | |
func resume() { | |
if state == .resumed { | |
return | |
} | |
state = .resumed | |
timer.resume() | |
} | |
func suspend() { | |
if state == .suspended { | |
return | |
} | |
state = .suspended | |
timer.suspend() | |
} | |
//MARK: - Private methods | |
private func subscribeToAppNotifications() { | |
notificationCenter.addObserver(self, selector: #selector(handleWillResignActiveNotification), name: NSNotification.Name.UIApplicationWillResignActive, object: nil) | |
notificationCenter.addObserver(self, selector: #selector(handleDidBecomeActiveNotification), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil) | |
} | |
@objc | |
private func handleWillResignActiveNotification() { | |
guard timerCount != nil else { return } | |
suspend() | |
resignActiveDate = Date() | |
} | |
@objc | |
private func handleDidBecomeActiveNotification() { | |
guard timerCount != nil else { return } | |
let deltaInterval = Date().timeIntervalSinceNow - resignActiveDate.timeIntervalSinceNow | |
timerCount = timerCount! - Int(deltaInterval) | |
resume() | |
} | |
} | |
Expample usage with timer: | |
///Need strong reference to timer somewhere outside | |
var timer = RepeatingTimer(timeInterval: 1) | |
func setupTimer(with timeCount: Int) { | |
timer.timerCount = timeCount | |
timer.eventHandlerOnMain = { [weak self] in | |
guard let strongSelf = self else { return } | |
strongSelf.updateTimer(timerCount: strongSelf.timer.timerCount!) | |
} | |
timer.timerFinishEventHandler = { [weak self] in | |
self?.stopTimerAndUpdateTimeLabel() | |
} | |
timer.resume() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment