I'm trying to use a DispatchSourceTimer to run a repeating timer on another thread. This code runs fine in a playground, however in my iOS app it keeps crashing either on the deinit method (or if I remove the deinit it crashes on dealloc for the thread its running on) and I can't figure out why. Is there a better way to use DispatchSourceTimer?
import UIKit
class DispatchTest {
var timer: DispatchSourceTimer
var count: Int = 0
init(timeInterval: TimeInterval) {
timer = DispatchSource.makeTimerSource(flags: .strict, queue: DispatchQueue.global(qos: .default))
timer.schedule(deadline: .now() + timeInterval, repeating: timeInterval, leeway: .milliseconds(100))
}
func startTimer() {
timer.setEventHandler(handler: {[weak self] in
self?.count += 1
if let count = self?.count {
print(count)
}
})
timer.resume()
}
deinit {
timer.setEventHandler {}
timer.cancel()
}
func stopTimer() {
self.timer.cancel()
}
}
let dispatch = DispatchTest(timeInterval: 1)
dispatch.startTimer()
I was experiencing the same problem. After digging around I have found the answer in Apple's Dispatch documentation.
https://developer.apple.com/documentation/dispatch/1452801-dispatch_suspend :
It is a programmer error to release an object that is currently suspended, because suspension implies that there is still work to be done. Therefore, always balance calls to this method with a corresponding call to dispatch_resume before disposing of the object. The behavior when releasing the last reference to a dispatch object while it is in a suspended state is undefined.
https://developer.apple.com/documentation/dispatch/1452929-dispatch_resume :
New dispatch event source objects returned by dispatch_source_create have a suspension count of 1
What I have deduced from this is that all new DispatchSource objects start suspended and thus the crash happens if you have not resumed the timer before deallocation.
The solution is to make sure the timer is in a resumed state before it is released.
I'm trying to perform an action when the playback reaches a certain time. I can't find any delegate methods or examples of how to do this. How can I call a method when the playback reaches a certain point?
That should help you addBoundaryTimeObserverForTimes:queue:usingBlock:
Requests the invocation of a block when specified times are traversed
during normal playback.
Objective C:
- (id)addBoundaryTimeObserverForTimes:(NSArray<NSValue *> *)times
queue:(dispatch_queue_t)queue
usingBlock:(void (^)(void))block;
Swift:
func addBoundaryTimeObserver(forTimes times: [NSValue],
queue: DispatchQueue?,
using block: #escaping () -> Void) -> Any
Usage:
_ = self.player.addBoundaryTimeObserver(forTimes: times, queue: DispatchQueue.main, using: {
[weak self] time in
// Your code goes here
})
Adding on some more details.
You can addBoundaryTimeObserver(forTimes:queue:using:) for a certain time or use addPeriodicTimeObserver(forInterval:queue:using:) for periodic intervals.
// for specific time
func addTimeObserver() {
var times = [NSValue]()
var currentTime = kCMTimeZero // make your time here
times.append(NSValue(time:currentTime))
// Queue on which to invoke the callback
let mainQueue = DispatchQueue.main
// Add time observer
timeObserverToken =
player.addBoundaryTimeObserver(forTimes: times, queue: mainQueue) {
[weak self] time in
// Update UI
}
}
To fire every half second during normal playback
func addPeriodicTimeObserver() {
// Invoke callback every half second
let interval = CMTime(seconds: 0.5,
preferredTimescale: CMTimeScale(NSEC_PER_SEC))
let mainQueue = DispatchQueue.main
timeObserverToken =
player.addPeriodicTimeObserver(forInterval: interval, queue: mainQueue) {
[weak self] time in
// update UI
}}
How can I run a function every minute?
In JavaScript I can do something like setInterval, does something similar exist in Swift?
Wanted output:
Hello World once a minute...
var helloWorldTimer = NSTimer.scheduledTimerWithTimeInterval(60.0, target: self, selector: Selector("sayHello"), userInfo: nil, repeats: true)
func sayHello()
{
NSLog("hello World")
}
Remember to import Foundation.
Swift 4:
var helloWorldTimer = Timer.scheduledTimer(timeInterval: 60.0, target: self, selector: #selector(ViewController.sayHello), userInfo: nil, repeats: true)
#objc func sayHello()
{
NSLog("hello World")
}
If targeting iOS version 10 and greater, you can use the block-based rendition of Timer, which simplifies the potential strong reference cycles, e.g.:
weak var timer: Timer?
func startTimer() {
timer?.invalidate() // just in case you had existing `Timer`, `invalidate` it before we lose our reference to it
timer = Timer.scheduledTimer(withTimeInterval: 60.0, repeats: true) { [weak self] _ in
// do something here
}
}
func stopTimer() {
timer?.invalidate()
}
// if appropriate, make sure to stop your timer in `deinit`
deinit {
stopTimer()
}
While Timer is generally best, for the sake of completeness, I should note that you can also use dispatch timer, which is useful for scheduling timers on background threads. With dispatch timers, since they're block-based, it avoids some of the strong reference cycle challenges with the old target/selector pattern of Timer, as long as you use weak references.
So:
var timer: DispatchSourceTimer?
func startTimer() {
let queue = DispatchQueue(label: "com.domain.app.timer") // you can also use `DispatchQueue.main`, if you want
timer = DispatchSource.makeTimerSource(queue: queue)
timer!.schedule(deadline: .now(), repeating: .seconds(60))
timer!.setEventHandler { [weak self] in
// do whatever you want here
}
timer!.resume()
}
func stopTimer() {
timer = nil
}
For more information, see the the Creating a Timer section of Dispatch Source Examples in the Dispatch Sources section of the Concurrency Programming Guide.
For Swift 2, see previous revision of this answer.
If you can allow for some time drift here's a simple solution executing some code every minute:
private func executeRepeatedly() {
// put your code here
DispatchQueue.main.asyncAfter(deadline: .now() + 60.0) { [weak self] in
self?.executeRepeatedly()
}
}
Just run executeRepeatedly() once and it'll be executed every minute. The execution stops when the owning object (self) is released. You also can use a flag to indicate that the execution must stop.
Here's an update to the NSTimer answer, for Swift 3 (in which NSTimer was renamed to Timer) using a closure rather than a named function:
var timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) {
(_) in
print("Hello world")
}
You can use Timer (swift 3)
var timer = Timer.scheduledTimerWithTimeInterval(60, target: self, selector: Selector("function"), userInfo: nil, repeats: true)
In selector() you put in your function name
In swift 3.0 the GCD got refactored:
let timer : DispatchSourceTimer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main)
timer.scheduleRepeating(deadline: .now(), interval: .seconds(60))
timer.setEventHandler
{
NSLog("Hello World")
}
timer.resume()
This is specially useful for when you need to dispatch on a particular Queue. Also, if you're planning on using this for user interface updating, I suggest looking into CADisplayLink as it's synchronized with the GPU refresh rate.
Here is another version algrid's answer with an easy way to stop it
#objc func executeRepeatedly() {
print("--Do something on repeat--")
perform(#selector(executeRepeatedly), with: nil, afterDelay: 60.0)
}
Here's an example of how to start it and stop it:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
executeRepeatedly() // start it
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NSObject.cancelPreviousPerformRequests(withTarget: self) // stop it
}
timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true, block: myMethod)
func myMethod(_:Timer) {
...
}
or
timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { _ in
...
}
make sure to invalid the timer at some point like your time is no longer visible, or you object is deist
I have a timer declared as the following:
var timer: DispatchSourceTimer?
override func viewDidLoad() {
super.viewDidLoad()
...
timer = DispatchSource.makeTimerSource()
timer!.scheduleRepeating(deadline: .now() + .seconds(60), interval: .seconds(3600), leeway: .seconds(1))
timer!.setEventHandler { [weak self] in
self?.saveData()
}
timer!.resume()
print("TIMER HAS STARTED")
}
This code is executed in my viewDidLoad method, and fires correctly the first time after the 1 min delay I included. It is then supposed to fire every hour. I ran my app in the simulator keeping it constantly on and in focus. In fact it is still running.
The first time the data was saved I logged a time of 11:45am. It was not saved again until 1:00pm, and then at 2:08. What am I missing?
EDIT: When testing this timer I was able to get data every minute, but for the actual application I need it to do it every hour.
In my case, the DispatchSourceTimer does not fire the event when I make an instance of it inside the viewDidLoad, not really sure why, tried it multiple times.
So just make sure you have your DispatchSourceTimer as a property, and also you could just try to shorten the interval and the delay parameter of the DispatchSourceTimer's schedule for the sake of testing, like so:
// MARK: - Properties
var timer: DispatchSourceTimer!
// MARK: - Functions
// MARK: Overrdes
override func viewDidLoad() {
super.viewDidLoad()
timer = DispatchSource.makeTimerSource()
timer.schedule(deadline: .now() + .seconds(1), repeating: .seconds(5), leeway: .seconds(1))
timer.setEventHandler(handler: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.saveData()
})
timer.resume()
}
func saveData() {
print("❇️Invoked...")
}
I have this object:
lockWallTask = DispatchWorkItem(block: {
self.lockWall()
})
DispatchQueue.main.asyncAfter(deadline: .now() + 10, execute: lockWallTask)
So it is executing after 10 seconds. However, I am trying to pause this item and then resume it. For instance:
I would pause the item after 3 seconds, meaning there is 7 seconds left for execution. I do other stuff for like 5 minutes and then I resume the item and there is still 7 seconds left for execution. I was trying to achieve this life this:
DispatchQueue.resume(task)
DispatchQueue.suspend(task)
However, I was given this compile error:
I don't understand that error. The variable task is of type 'DispatchWorkItem'
How would I achieve pausing, or suspending, a DispatchWorkItem and then resuming it?
You need to use DispatchSource timer where you can pause, resume and stop and perform your operation accordingly.
var timer: DispatchSource?
func resumeTimer() {
guard let timer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags(rawValue: 0), queue: mainQueue) as? DispatchSource else { return }
timer.setEventHandler {
mainQueue.async(execute: { [weak self] in
self?.runTimer()
})
}
timer.scheduleRepeating(deadline: .now(), interval: .seconds(10), leeway: .seconds(1))
timer.resume()
}
func pausedTimer() {
if let timer = timer {
timer.suspend()
}
}
func stoppedTimer() {
if let timer = timer {
timer.cancel()
}
}