How to disable the first fire of the dispatch source timer - ios

I am using Dispatch source timer.
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
However, I find that the block is called almost immediately after the code above is run over. After that the timer fires every interval.
My question is how to disable the first fire?

The second argument to dispatch_source_set_timer is the time when the timer should fire for the first time. You're setting it to dispatch_walltime(NULL, 0), i.e. "now".
To make it fire for the first time after some interval, pass dispatch_walltime(NULL, interval) instead.

This could happen when dispatch time is in past or is now. Check your dispatch time.

Related

Why is my timer's tolerance always halved?

In my app, I'm scheduling a timer like this:
let period = TimeInterval(10)
let timer = Timer.scheduledTimer(withTimeInterval: period, repeats: true, block: { [weak self] (_) in
// Some repeating code here
})
timer.tolerance = period
Essentially, I want a timer to fire once in every consecutive, repeating 10 second period, but it doesn't matter when the timer fires in each individual period. However, if I set a breakpoint in the debugger for immediately after this code runs. I can see that my timer's timeInterval is set to 10 seconds, but the timer's tolerance is set to 5. I've played around with various values for period, but no matter the case, it seems that my timer's tolerance will always be half of its timeInterval. Why is that? Will this still produce the functionality I intend? If not, how can I prevent this from happening?

How to loop despatch_after

I want to loop dispatch_after for looping showing images like this:
while isRunning {
let delay = Int64(Double(NSEC_PER_SEC) * settings.delay)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_main_queue(), {
dispatch_async(dispatch_get_main_queue(), {
self.showNextImage()
})
})
}
It supposed to call showNextImage every delay seconds. But it stucks in infinite loop without showing images. I dont know how to solve this problem. Any help is appreciated.
Loop is dispatching infinite dispatch_after because isRunning is yes and loop is not waiting , better to put some count (i<10) or use NSTimer to certain condition then invalidate.
if (self.stimerShowNextImage.isValid) {
[self.stimerShowNextImage invalidate];
self.stimerShowNextImage = nil;
}
self.stimerShowNextImage = [NSTimer scheduledTimerWithTimeInterval: 5
target: self
selector: #selector(showNextImage)
userInfo: nil
repeats: YES];
Above is the timerCode with 5 second delay , in ViewWillDisapper you need to invalidate it , Also before scheduling timer you need to check its not already running.
Look in your code dispatch_after already running in main thread. And again inside main thread using. i mean not need.
when it is need means if u r using other than main thread outside and after u r using uielements, that time use main thread inside.

Why is decreasing interval not speeding up iOS timer execution?

When I run this timer code for 60 seconds duration/1 sec interval or 6 seconds/.1 sec interval it works as expected (completing 10X faster). However, decreasing the values to 0.6 seconds/.01 seconds doesn't speed up the overall operation as expected (having it complete another 10X faster).
When I set this value to less than 0.1 it doesn't work as expected:
// The interval to use
let interval: NSTimeInterval = 0.01 // 1.0 and 0.1 work fine, 0.01 does not
The rest of the relevant code (full playground here: donut builder gist):
// Extend NSTimeInterval to provide the conversion functions.
extension NSTimeInterval {
var nSecMultiplier: Double {
return Double(NSEC_PER_SEC)
}
public func nSecs() -> Int64 {
return Int64(self * nSecMultiplier)
}
public func nSecs() -> UInt64 {
return UInt64(self * nSecMultiplier)
}
public func dispatchTime() -> dispatch_time_t {
// Since the last parameter takes an Int64, the version that returns an Int64 is used.
return dispatch_time(DISPATCH_TIME_NOW, self.nSecs())
}
}
// Define a simple function for getting a timer dispatch source.
func repeatingTimerWithInterval(interval: NSTimeInterval, leeway: NSTimeInterval, action: dispatch_block_t) -> dispatch_source_t {
let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue())
guard timer != nil else { fatalError() }
dispatch_source_set_event_handler(timer, action)
// This function takes the UInt64 for the last two parameters
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, interval.nSecs(), leeway.nSecs())
dispatch_resume(timer)
return timer
}
// Create the timer
let timer = repeatingTimerWithInterval(interval, leeway: 0.0) { () -> Void in
drawDonut()
}
// Turn off the timer after a few seconds
dispatch_after((interval * 60).dispatchTime(), dispatch_get_main_queue()) { () -> Void in
dispatch_source_cancel(timer)
XCPlaygroundPage.currentPage.finishExecution()
}
The interval you set for a timer is not guaranteed. It is simply a target. The system periodically checks active timers and compares their target fire time to the current time and if the fire time has passed, it fires the timer. But there is no guarantee as to how rapidly the system is checking the timer. So the shorter the target interval and the more other work a thread is doing, the less accuracy a timer will have. From Apple's documentation:
A timer is not a real-time mechanism; it fires only when one of the
run loop modes to which the timer has been added is running and able
to check if the timer’s firing time has passed. Because of the various
input sources a typical run loop manages, the effective resolution of
the time interval for a timer is limited to on the order of 50-100
milliseconds. If a timer’s firing time occurs during a long callout or
while the run loop is in a mode that is not monitoring the timer, the
timer does not fire until the next time the run loop checks the timer.
Therefore, the actual time at which the timer fires potentially can be
a significant period of time after the scheduled firing time.
This does indeed appear to be a playground limitation. I'm able to achieve an interval of 0.01 seconds when testing on an actual iOS device.
Although I was wrong in my initial answer about the limitation of the run loop speed – GCD is apparently able to work some magic behind the scenes in order to allow multiple dispatch sources to be fired per run loop iteration.
However, that being said, you should still consider that the fastest an iOS device's screen can refresh is 60 times a second, or once every 0.0167 seconds.
Therefore it simply makes no sense to be doing drawing updates any faster than that. You should consider using a CADisplayLink in order to synchronise drawing with the screen refresh rate – and adjusting your drawing progress instead of timer frequency in order to control the speed of progress.
A fairly rudimentary setup could look like this:
var displayLink:CADisplayLink?
var deltaTime:CFTimeInterval = 0
let timerDuration:CFTimeInterval = 5
func startDrawing() {
displayLink?.invalidate()
deltaTime = 0
displayLink = CADisplayLink(target: self, selector: #selector(doDrawingUpdate))
displayLink?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
}
func doDrawingUpdate() {
if deltaTime >= timerDuration {
deltaTime = timerDuration
displayLink?.invalidate()
displayLink = nil
}
draw(CGFloat(deltaTime/timerDuration))
deltaTime += displayLink?.duration ?? 0
}
func draw(progress:CGFloat) {
// do drawing
}
That way you can ensure that you're drawing at the maximum frame-rate available, and your drawing progress won't be affected if the device is under strain and the run loop is therefore running slower.

Timing issue with dispatch_source_t handler function - am I following the right pattern for dispatch timer?

I read the documentation and came to know that timer (dispatch_source_t) skips to fire if the handler is still in progress for previous iterations.
But this whole business of handler taking it longer makes this inaccurate. And I am observing that I am unable to stop the timer at intended times.
My code looks like this:
double secondsToFire = 1.0f;
dispatch_queue_t queue = dispatch_get_main_queue();
m_myTimer = CreateDispatchTimer(secondsToFire, queue,
^{
//Do some time consuming operation, taking any number of seconds
int retVal = DoSomeOperation();
if (retVal == 1)
{
cancelTimer(m_myTimer);
SomeOtherOperation();
}
});
dispatch_source_t CreateDispatchTimer(double interval, dispatch_queue_t queue, dispatch_block_t block)
{
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timer)
{
// dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), interval * NSEC_PER_SEC, (1ull * NSEC_PER_SEC) / 10);
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
void cancelTimer(dispatch_source_t _timer)
{
if (_timer)
{
dispatch_source_cancel(_timer);
_timer = nil;
}
}
Note:
Inside DoSomeOperation(), I have code enclosed with #synchronized(array), in which I access an array who is being written by another private queue. But entire DoSomeOperation() is executed on main queue.
My question is, is this is the right and accurate timing model? I am posting here because I am facing lot of inaccuracies - timer doesn't fire every second, and it doesn't stop as intended too. I am able to observe that SomeOtherOperation() gets called when retVal == 1, but timer isn't done yet.
Another Note:
m_myTimer above is an iVar, and my project is ARC, if that could make any difference.
No, a dispatch timer doesn't get "skipped" if it fires while your handler is running, the handler will get re-invoked for the pending event right away once the previous invocation returns.
If multiple firings occur while the handler is running or enqueued (or while the source is suspended), they will get all get coalesced into a single handler invocation (as is the case for all edge-triggered source types).
You can check how many firings a given handler invocation is for with dispatch_source_get_data()
My concern was about accuracy in starting, firing and stopping, and not in correct reporting of things.
I finally ended up assigning one of my tasks off a private queue instead of main one. The advantage could be clearly visible in timer accuracy and prompt timer cancellation.
Conclusion:
More the same (esp. main) queue getting flogged by timer tasks, more they become inaccurate.

iOS - dispatcherTimer blocking touches events?

I am using the dispatcher source timer update a view at different frame rates. (8, 12 or 24 FPS)
Here is the code that initializes the dispatcherTimer and the function used to create the timer.
(this function is directly taken from apple doc in the subsection "creating a timer": http://developer.apple.com/library/mac/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/GCDWorkQueues/GCDWorkQueues.html)
call:
self.dispatchTimer = [self createDispatchTimerWithInterval:self.project.frameDuration * NSEC_PER_SEC
leeway:0.0 * NSEC_PER_SEC
queue:dispatch_get_main_queue()
block:displayFrame];
function:
- (dispatch_source_t)createDispatchTimerWithInterval:(uint64_t)interval leeway:(uint64_t)leeway queue:(dispatch_queue_t)queue block:(dispatch_block_t)block {
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
0, 0, queue);
if (timer) {
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
My view updates perfectly, but the touch events are not caught. My first bet would be that the block "displayFrame" takes too much processing time because if I reduce the frameDuration to 0.5 second or so, the touch events are caught.
I only tested this on iOS 4 with a iPad 2.
Any help or hint would be greatly appreciated!
Etienne
UPDATE
I have asked a similar question on the apple developper forum, here is the answer I got: https://devforums.apple.com/thread/156633?tstart=0
The main run loop drains the main queue after each pass through the runloop. I think you're right when you say your duration is too short. If the source is adding new blocks to the queue faster than they can be drained, I would certainly expect the runloop to never resume processing events (since it's constantly trying to drain the queue).

Resources