Will GCD Timer not work like NSTimer under some condition? - ios

As we all know, under NSDefaultRunLoopMode, NSTimer will not fire when the scrollView is scrolling. If we need the timer works, we should add it to NSRunLoopCommonModes.
Consider that,
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, aQueue);
dispatch_resume(timer); // Is the place for `dispatch_resume(timer);` appropriate? Or we should
always put it after `dispatch_source_set_event_handler`?
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, ti * NSEC_PER_SEC, ti * 0.1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
if (anAction) anAction();
});
when I create timer based on GCD like above, will the timer be hang or ignored, in other words could not fire, under some condition? Or it will run forever?
Here's my thinking:
What if the queue is suspended?
If it doesn't run on the queue, the timer will continue running regardless of the queue's status.
If it does run on the queue, the timer is suspended as well as the queue.
Where does the timer run?
According to the doc, dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, aQueue); means :
Creates a new dispatch source to monitor low-level system objects and
automatically submit a handler block to a dispatch queue in response
to events.
it says that we submit the event handler to the argument dispatch_queue_t queue, that is we will respond to the event on the queue. But it doesn't mean the timer runs on the queue.
Thanks :)

Related

performSelectorWithDelay in serialQueue

I've a serial queue and I use that queue to call a performSelectorWithDelay like below
dispatch_async(serialQueue, ^(void) {
[self performSelector:#selector(fetchConfigFromNetwork) withObject:nil afterDelay:rootConfig.waitTime];
});
However, the method fetchConfigFromNetwork never gets called. However, if instead of serialQueue, I use mainQueue - it starts working.
Cannot understand what's happening here and how to fix it?
The explanation why your code doesn't work is in the documentation: https://developer.apple.com/documentation/objectivec/nsobject/1416176-performselector?language=occ
This method registers with the runloop of its current context, and
depends on that runloop being run on a regular basis to perform
correctly. One common context where you might call this method and end
up registering with a runloop that is not automatically run on a
regular basis is when being invoked by a dispatch queue. If you need
this type of functionality when running on a dispatch queue, you
should use dispatch_after and related methods to get the behavior you
want.
I'm assuming you want that method to be called on the serial queue with a delay. The most straight forward (and recommended way) is to use dispatch_after:
__weak typeof(self) wself = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(rootConfig.waitTime * NSEC_PER_SEC)), serialQueue, ^{
[wself fetchConfigFromNetwork];
});
This method sets up a timer to perform the aSelector message on the current thread’s run loop. The timer is configured to run in the default mode (NSDefaultRunLoopMode). When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in the default mode; otherwise, the timer waits until the run loop is in the default mode.
This is the discussion about the method performSelector:withObject:afterDelay:, I think the block of dispatch_async will execute on a new thread (not main thread), but you would not know which thread it is, so you can not new a runloop and open it and assign it to this thread. because the runloop of thread is close in default except the main thread, the timer will wait forever.
On my opinion, you should use NSThread instead of dispatch_async, and create a runloop for the thread that you use, then specified the mode of runloop with NSDefaultRunLoopMode, if you actually want to cancelPreviousPerformRequestsWithTarget, otherwise use dispatch_after instead of performSelector.
That's my understanding. I can't promise it is right.

Suspending serial queue

Today i've tried following code:
- (void)suspendTest {
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_BACKGROUND, 0);
dispatch_queue_t suspendableQueue = dispatch_queue_create("test", attr);
for (int i = 0; i <= 10000; i++) {
dispatch_async(suspendableQueue, ^{
NSLog(#"%d", i);
});
if (i == 5000) {
dispatch_suspend(suspendableQueue);
}
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(#"Show must go on!");
dispatch_resume(suspendableQueue);
});
}
The code starts 10001 tasks, but it should suspend the queue from running new tasks halfway for resuming in 6 seconds. And this code works as expected - 5000 tasks executes, then queue stops, and after 6 seconds it resumes.
But if i use a serial queue instead of concurrent queue, the behaviour is not clear for me.
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_BACKGROUND, 0);
In this case a random number of tasks manage to execute before suspending, but often this number is close to zero (suspending happens before any tasks).
The question is - Why does suspending work differently for serial and concurrent queue and how to suspend serial queue properly?
As per its name, the serial queue performs the tasks in series, i.e., only starting on the next one after the previous one has been completed. The priority class is background, so it may not even have started on the first task by the time the current queue reaches the 5000th task and suspends the queue.
From the documentation of dispatch_suspend:
The suspension occurs after completion of any blocks running at the time of the call.
i.e., nowhere does it promise that asynchronously dispatched tasks on the queue would finish, only that any currently running task (block) will not be suspended part-way through. On a serial queue at most one task can be "currently running", whereas on a concurrent queue there is no specified upper limit. edit: And according to your test with a million tasks, it seems the concurrent queue maintains the conceptual abstraction that it is "completely concurrent", and thus considers all of them "currently running" even if they actually aren't.
To suspend it after the 5000th task, you could trigger this from the 5000th task itself. (Then you probably also want to start the resume-timer from the time it is suspended, otherwise it is theoretically possible it will never resume if the resume happened before it was suspended.)
I think the problem is that you are confusing suspend with barrier. suspend stops the queue dead now. barrier stops when everything in the queue before the barrier has executed. So if you put a barrier after the 5000th task, 5000 tasks will execute before we pause at the barrier on the serial queue.

Is it safe to have a timer dispatch source scheduled every second?

I have this timer created using GDC. It will call a method every 1 second. Is it safe to have this timer alive during the whole time, even in background?
self.theTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(self.theTimer, DISPATCH_TIME_NOW, (1.0) * NSEC_PER_SEC, 0.25 * NSEC_PER_SEC);
dispatch_source_set_event_handler(self.theTimer, ^{
[self awakeAndProcess];
});
// Start the timer
dispatch_resume(self.theTimer);
The method "awakeAndProcess" has a "consumer" behavior where it checks a data queue and tries to send an HTTP request. So it is constantly checking if there are messages to be sent
You are better off pausing the timer when going to background to conserve battery because the awakeAndProcess seems to be a network call. But if you are in background then all your tasks are suspended anyways so shouldn't be a problem. When in foreground its better to wait for the previous awakeAndProcess call to finish before you trigger the next call. Otherwise you might end up with lot of awakeAndProcess calls being batched together. If awakeAndProcess is not reentrant then it can cause havoc in your code.
You are better off suspending the timer after the awakeAndProcess and then call resume after the awakeAndProcess is fully complete.
If you do this then its safe to use your approach.

iOS app runs for only 1 second in background

I have 2 classes, ViewController class, and Worker. All the code that I need to run in the background is in the Worker class.
My ViewController looks something like this:
- (void)viewDidLoad {
//create an instance of 'Worker'
}
- buttonClick {
//call the 'manager' method in the worker instance that was just created (do this method as a background thread)
}
My Worker class looks something like this:
- (void)manager {
//call 'repeat' method as a background thread
}
- (void)repeat {
//call 'innerWorker' method as a background thread
}
- (void)innerWorker {
//do work
}
The repeat method needs to get run every second.
I've tried the 2 following ways to make the repeat method run every second.
Method 1 :
timerObj = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(repeatMethod) userInfo:nil repeats:YES];
Method 2:
Putting this code at the end of repeat:
dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, q_background, ^(void){
[self repeatMethod];
});
Both of these 2 methods work fine as long as the app is in the foreground but the moment I press the home button, the repeat method runs one last time, but does not call the innerWorker method and then the app is suspended. I know this by using NSLogs all over the place.
I realize Method 2 is a bit of a hack but that's fine as this is an internal app that I wont be publishing.
All the methods are called as background threads using this code: eg:
dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(q_background, ^{
[self repeatMethod];
});
I'm new to iOS so maybe I'm missing something small here. I just want my app to keep running in the background. Please help me.
First of all: the dispatch_queue priority has nothing to do with running in background ... it is the priority in the queue.
Here are the info:
DISPATCH_QUEUE_PRIORITY_HIGH Items dispatched to the queue will run at high priority, i.e. the queue will be scheduled for execution before any default priority or low priority queue.
DISPATCH_QUEUE_PRIORITY_DEFAULT Items dispatched to the queue will run at the default priority, i.e. the queue will be scheduled for execution after all high priority queues have been scheduled, but before any low priority queues have been scheduled.
DISPATCH_QUEUE_PRIORITY_LOW Items dispatched to the queue will run at low priority, i.e. the queue will be scheduled for execution after all default priority and high priority queues have been scheduled.
DISPATCH_QUEUE_PRIORITY_BACKGROUND Items dispatched to the queue will run at background priority, i.e. the queue will be scheduled for execution after all higher priority queues have been scheduled and the system will run items on this queue on a thread with background status as per setpriority(2) (i.e. disk I/O is throttled and the thread's scheduling priority is set to lowest value).
For "real" background operations check this:
https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html
The answer to your question depends on what you want to do.
Only the following types of apps, with the appropriate plist flag set, can execute a background process while the app is still in the foreground:
Apps that play audible content to the user while in the background,
such as a music player app
Apps that record audio content while in the background
Apps that keep users informed of their location at all times, such
as a navigation app
Apps that support Voice over Internet Protocol (VoIP)
Apps that need to download and process new content regularly
Apps that receive regular updates from external accessories
If your app falls outside of one of those categories, and will go through app store approval, you will need to devise another strategy for doing the background work you desire to preform.
More info can be found here:
https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html#//apple_ref/doc/uid/TP40007072-CH4-SW3

Stop a NSRunLoop in global queue

I have just created a background task with a timer using NSRunLoop and NSTimer in my ViewController:
- (void)runBackgroundTask: (int) time{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(startTrackingBg) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
}
To call a function that will verify token validity, etc. Is it possible to end this loop from inside the function? For instance:
-(void)startTrackingBg
{
if(TOKEN IS NOT VALID)
{
STOP_THREAD;
dispatch_sync(dispatch_get_main_queue(), ^{
[self alertStatus:#"Session Lost!" :#"Error!"];
[self popToLogin];
});
}
}
A couple of thoughts:
If you look at the documentation for run, they show a pattern that solves your problem:
If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:. In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.
Manually removing all known input sources and timers from the run loop is not a guarantee that the run loop will exit. OS X can install and remove additional input sources as needed to process requests targeted at the receiver’s thread. Those sources could therefore prevent the run loop from exiting.
If you want the run loop to terminate, you shouldn't use this method. Instead, use one of the other run methods and also check other arbitrary conditions of your own, in a loop. A simple example would be:
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
where shouldKeepRunning is set to NO somewhere else in the program.
Having said that, if I were going to start another thread, I wouldn't use up one of the global worker threads, but rather I'd just instantiate my own NSThread.
More critically, depending upon what you're trying to do in this other thread, there are generally much better other patterns than establishing your own run loop.
For example, if I wanted to have timer run something in another queue, I'd use a dispatch timer instead:
#property (nonatomic, strong) dispatch_source_t timer;
and then instantiate and start dispatch timer source to run on your designated GCD queue:
dispatch_queue_t queue = dispatch_queue_create("com.domain.app.polltimer", 0);
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, 0), kPollFrequencySeconds * NSEC_PER_SEC, 1ull * NSEC_PER_SEC);
dispatch_source_set_event_handler(self.timer, ^{
<#code to be run upon timer event#>
});
dispatch_resume(self.timer);
Or, if you want to use NSTimer, just schedule that on the main runloop, and have the method it calls dispatch the time consuming task to the background queue at that time. But, either way, I'd avoid adding the overhead of a second run loop.
Having shown you better ways to use timers in background threads, now that you describe the intent (polling a server) I'd actually recommend against using timer at all. Timers are useful when you want some action to be initiated at some regular interval. But in this case, you probably want to initiate the next server request after a certain amount of time after the previous request finished. So, in the completion block of the previous request, you might do something like:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 20.0 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
<#code to initiate next request#>
});
Also, I'd personally want to make sure there was a very compelling reason to polling your server. Polling always seems so intuitively appealing and logical, but it is an extravagant use of the user's battery, CPU and data plan. And in those cases where you need the client to respond to server changes quickly, there are often better architectures (sockets, push notifications, etc.).
You do a dispatch async and inside you add a timer to the runloop to run a method periodically? You should add threads to make sure that you use all systems of parallelism at once. ;-)
Seriously: Use dispatch_after() and decide inside the block if you want to do it again.

Resources