Is `dispatch_source_cancel(timer)` sufficient to cancel recurring task? - ios

I have a recurring task I want to run in my ios swift.
below is my code to repeat a task.
How can i stop the re-fresh task?
Am i missing something in the stopRefreshTimer()
Even if i don't miss anything, can it be that an old scheduled task will run even after I called stopRefreshTimer() ? Or is the halt immediate.
func startRefreshTimer() {
if (timer != nil) {return }
let queue = dispatch_queue_create("com.domain.app.timer", nil)
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC, 1 * NSEC_PER_SEC) // every 60 seconds, with leeway of 1 second
dispatch_source_set_event_handler(timer) {
if (self.timer == nil) {return}
self.getLineState(self.lineName)
}
dispatch_resume(timer)
}
func stopRefreshTimer() {
if (timer != nil)
{
dispatch_source_cancel(timer)
timer = nil
}
}

Related

Set a timer with variable intervals in swift

I need to set a specific timer asynchronously after executing an action like this:
calling my function (sending http request)
10 seconds after, sending another request
20 seconds after 2), sending another one
40 seconds after 3), another one
then send every 60 seconds another one
At any moment, I must be able to cancel my timer. Firstable I thought using DispatchQueue, but I see several post saying that it's not possible to cancel it.
Some post suggest to use DispatchWorkItem ( how to stop a dispatchQueue in swift ) but I'm not sur it fit my need (unless adding a sleep(10,20,40,60...) in each loop but will it not impact asynchronous part?).
Another answer from this post suggest to use Timer instead ( scheduledTimerWithTimeInterval ) with repeats:false, and invalidate it after each loop, but I didn't undertand how to do the loop in this case. Actually, here's my code, that just send a request after 10 seconds:
private func start() {
timer?.invalidate()
if(self.PCount > self.Intervals.count){
self.value = self.pollingIntervals.count-1
} else {
self.Value = self.Intervals[self.pCount]
}
print("set timer with \(pollingValue) as interval")
timer = Timer.scheduledTimer(withTimeInterval: TimeInterval(pollingValue), repeats: false, block: { timer in
self.sessionManager.sendHit()
self.pollingCount+=1
})
}
The current goal is to do something like coroutine in Kotlin, like it work with this code :
private val Intervals = longArrayOf(10000,20000,40000,60000)
private var Count = 0
private fun start() {
currentJob = GlobalScope.launch {
while (true) {
delay(Intervals[if (Count > Intervals.size) Intervals.size - 1 else Count]) // 10,20,40 then every 60
session.sendHit()
pollingCount++
}
}
}
I'm not sure what solution is the most appropriate to my project
Here is a basic idea on how to approach the problem
struct RequestMananger {
var timers: [Timer] = []
mutating func startSequence() {
var delay = 10.0
sendRequest()
timers.append(scheduleTimer(delay))
delay += 20
timers.append(scheduleTimer(delay))
delay += 40
timers.append(scheduleTimer(delay))
delay += 60
timers.append(scheduleTimer(delay, repeats: true))
}
private func scheduleTimer(_ delay: TimeInterval, repeats: Bool = false) -> Timer {
return Timer.scheduledTimer(withTimeInterval: delay, repeats: false, block: { timer in
self.sendRequest()
})
}
func sendRequest() {
}
func cancelTimers() {
timers.forEach { $0.invalidate() }
}
}

Why does the second call to my function get triggered twice? (Swift iOS timer/speechrecognition)

self.adaResponseApi runs twice when the timer hits 1.5 seconds after the last recorded speech input. It should only run once.
It is specifically running from the 1.5 interval instantiation and not from the first instantiation, which is triggered when the user specifically-stops speech input.
recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest, resultHandler: { (result, error) in
if result != nil {
self.textView.text = result?.bestTranscription.formattedString
self.isFinal = (result?.isFinal)!
}
if let timer = self.detectionTimer, (timer.isValid) {
if self.isFinal! {
self.detectionTimer?.invalidate()
self.adaResponseApi()
self.isFinal = true
}
} else { // if self.isFinal == false
self.detectionTimer = Timer.scheduledTimer(withTimeInterval: 1.5, repeats: false, block: { timer in
self.adaResponseApi() // This call gets triggered twice and for the life of me I can't figure out why.
timer.invalidate()
self.isFinal = true
})
}
})
OK here goes... I'm assuming that the request has shouldReportPartialResults set to true (default value).
recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { [weak self] (result, error) in
// [weak self] to stop memory leaks
guard let result = result else {
// no result means probably an error, you should report that to your user
return
}
// display the most accurate transcription (partial or final)
self?.textView.text = result.bestTranscription.formattedString
// invalidate timer
// result is final (and so should invalidate timer)
// or result is partial (and so will create new timer ... and invalidate this one)
self?.detectionTimer?.invalidate()
if result.isFinal {
// we have a final result... use it
self?.adaResponseApi()
} else {
// create timer for if the next response times out
self?.detectionTimer = Timer.scheduledTimer(withTimeInterval: 1.5, repeats: false) { [weak self] timer in
self?.adaResponseApi()
}
}
}
I think I would do it something like this...
I'm not 100% certain what your timer is there for so I assumed it's there in case the recogniser does not return a response quickly and so we can fall back to the most recent partial result?
I also don't think we should need to store the value of result.isFinal. If you do need that then you can add it to the line after self?.textView.text = result.bestTranscription.formattedString.

How to pause/resume a DispatchWorkItem in Swift

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()
}
}

NSTimer repeats getting blocked randomly? [duplicate]

I have a UIScrollView that has a series of labels which are rapidly updating numbers (every .06 seconds). While the scroll view is moving, however, the NSTimer is paused and does not continue until after the scrolling and the elastic animation have finished.
How can I avoid this and have the NSTimer run regardless of the state of the scroll view?
An easy way to fix this is adding your NSTimer to the mainRunLoop.
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
To remove a timer from all run loop modes on which it is installed, send an invalidate message to the timer.
for swift:
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
(Swift) An alternative: You can use a GCD-based timer system like this one:
class GCDTimer {
private var _timer : dispatch_source_t?
init() {
}
private func _createTheTimer(interval : Double, queue : dispatch_queue_t, block : (() -> Void)) -> dispatch_source_t
{
let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timer != nil)
{
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC))), UInt64(interval * Double(NSEC_PER_SEC)), (1 * NSEC_PER_SEC) / 10);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
func start(interval : Double, block : (() -> Void))
{
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_timer = _createTheTimer(interval, queue: queue, block: block)
}
func stop()
{
if (_timer != nil) {
dispatch_source_cancel(_timer!);
_timer = nil;
}
}
}

UIScrollView pauses NSTimer while scrolling

I have a UIScrollView that has a series of labels which are rapidly updating numbers (every .06 seconds). While the scroll view is moving, however, the NSTimer is paused and does not continue until after the scrolling and the elastic animation have finished.
How can I avoid this and have the NSTimer run regardless of the state of the scroll view?
An easy way to fix this is adding your NSTimer to the mainRunLoop.
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
To remove a timer from all run loop modes on which it is installed, send an invalidate message to the timer.
for swift:
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
(Swift) An alternative: You can use a GCD-based timer system like this one:
class GCDTimer {
private var _timer : dispatch_source_t?
init() {
}
private func _createTheTimer(interval : Double, queue : dispatch_queue_t, block : (() -> Void)) -> dispatch_source_t
{
let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timer != nil)
{
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC))), UInt64(interval * Double(NSEC_PER_SEC)), (1 * NSEC_PER_SEC) / 10);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
func start(interval : Double, block : (() -> Void))
{
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_timer = _createTheTimer(interval, queue: queue, block: block)
}
func stop()
{
if (_timer != nil) {
dispatch_source_cancel(_timer!);
_timer = nil;
}
}
}

Resources