Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A repeating GCD timer that can run on a background queue
/// RepeatingTimer mimics the API of DispatchSourceTimer but in a way that prevents
/// crashes that occur from calling resume multiple times on a timer that is
/// already resumed (noted by https://github.com/SiftScience/sift-ios/issues/52
class RepeatingTimer {
let timeInterval: TimeInterval
init(timeInterval: TimeInterval) {
self.timeInterval = timeInterval
}
private lazy var timer: DispatchSourceTimer = {
let t = DispatchSource.makeTimerSource()
t.schedule(deadline: .now() + self.timeInterval, repeating: self.timeInterval)
t.setEventHandler(handler: { [weak self] in
self?.eventHandler?()
})
return t
}()
var eventHandler: (() -> Void)?
private enum State {
case suspended
case resumed
}
private var state: State = .suspended
deinit {
timer.setEventHandler {}
timer.cancel()
/*
If the timer is suspended, calling cancel without resuming
triggers a crash. This is documented here https://forums.developer.apple.com/thread/15902
*/
resume()
eventHandler = nil
}
func resume() {
if state == .resumed {
return
}
state = .resumed
timer.resume()
}
func suspend() {
if state == .suspended {
return
}
state = .suspended
timer.suspend()
}
}
@scottymac

This comment has been minimized.

Copy link

scottymac commented Nov 6, 2017

Daniel - is this ok to use in a project? -- permission-wise, I mean

@danielgalasko

This comment has been minimized.

Copy link
Owner Author

danielgalasko commented May 7, 2018

totally fine @scottymac I was not sure how to license snippets but this is definitely code you can use under your own discretion :)

@rodrigo98rm

This comment has been minimized.

Copy link

rodrigo98rm commented Jan 20, 2019

Hi @danielgalasko,

I'm trying to use this class but something weird is happening.

I used this bit of code from your Medium article, but nothing was printed to the console:

let t = RepeatingTimer(timeInterval: 3)
        t.eventHandler = {
            print("Timer Fired")
        }
        t.resume()

But, (now comes the weird part) if I do this, then "Timer Fired" gets printed every 3 seconds:

let t = RepeatingTimer(timeInterval: 3)
        t.eventHandler = {
            print("Timer Fired")
            if(false){   //I know this makes no sense, but it works. Go figure...
                t.suspend()
            }
        }
        t.resume()

Any ideas?

@berkakkerman

This comment has been minimized.

Copy link

berkakkerman commented Apr 16, 2019

"eventHandler" never triggers. What am I missing? Is there a issue with capabilities of app or configuration.

@sdebaene

This comment has been minimized.

Copy link

sdebaene commented Apr 28, 2019

@rodrigo98rm It's because your timer 't' has been eaten by garbage collector before even starting. In your weird scenario, it's because your handler keeps a reference to 't' so the garbage collector don't eat it. You are probably running the test inside a func.

@Deco354

This comment has been minimized.

Copy link

Deco354 commented Jun 17, 2019

withExtendedLifetime(::) should keep it alive if you want to keep it as a local property.

Setting it to an instance property should also do the trick, but be sure to handle the retain cycle if you reference self inside the closure.

@Haibo-Zhou

This comment has been minimized.

Copy link

Haibo-Zhou commented Oct 26, 2019

Could I add Selector in your customer timer? like Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(self.trackAudio), userInfo: nil, repeats: true), because I need to track audio process and update app's progress bar in the selector method. Currently, timer doesn't resume from background, so I want to try yours custom timer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.