NSRunLoop and dispatch queue - ios

I have a couple of questions on NSRunLoops in conjunction with dispatch queues.
In my application's delegate applicaitonDidEnterBackground, I'm creating a UIBackgroundTask.
Following that, I use a timer and run loop to execute a task a few minutes after the app is in the background:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
[[NSRunLoop currentRunLoop] addTimer:self.myTimer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
So here are my questions:
Is the above a valid approach or is there a better way to achieve a scheduled task while in the background?
Do I need to stop the run loop once my timer's method has fired? I recall from Apple's documentation that simply calling run on an NSRunLoop is not advisable, but I also understand that in GCD I don't have fine-grain control over which thread is returned.
Thanks for your help.

Related

Why can not I stop a timer in dispatch_async serial queue?

It's simply an experimental code, but I got confused since the code didn't execute as I supposed.
The code is like:
- (void)viewDidLoad {
[super viewDidLoad];
self.myQueue = dispatch_queue_create("com.maxwell.timer", NULL);
dispatch_async(self.myQueue, ^{
self.timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(#"Hey!");
}];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
}
Now, I got a output "Hey!" every 1 second, no problem here. I do know that in a dispatched thread I have to run the runloop explicitly.
The problem came out when I tried to stop the timer.
- (void)stopTimer {
dispatch_async(self.myQueue, ^{
[self.timer invalidate];
self.Timer = nil;
});
}
Actually the code in block wouldn't even execute!
What's more, if I used concurrent queue here (dispatch_asyn(dispatch_get_global_queue(...), ^{...})) it would be all right.
Things I know: each time I dispatch_async, no matter concurrent or serial queue, the code execute in different thread. So strictly I didn't invalidate the timer in the same thread where I added it, but it did invalidate in concurrent thread.
So my question is why it failed to invalidate in serial queue?
The issue is that you have a serial queue on which you call [[NSRunLoop currentRunLoop] run]. But you’re not returning from that call (as long as there are timers and the like on that run loop). As the run documentation says:
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.
That has the effect of blocking your serial queue’s thread. Any code dispatched to that queue (such as your attempt to invalidate the timer) won’t run as long as that thread is blocked. You have a “Catch 22".
On top of that, if you’re going to set up a background thread to run a NSTimer, you’ll want to create your own thread for that, not use one of the GCD worker threads. See https://stackoverflow.com/a/38031658/1271826 for an example. But as that answer goes on to describe, the preferred method for running timers on a background thread are dispatch timers, getting you out of the weeds of manipulating threads and run loops.
I guess:
In a serial queue, a task is ready to execute only if its predecessor is finished. Here since a runloop which fires a timer is running, the task of invalidating the timer is waiting (blocked). So the code block is never executed.

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.

When should I apply Runloop to my program and why?

My requirement is that I want to call an API to ask for some new information from my server every 6 seconds,so I wrote my code as below:
MyBackgroundThread(){
while(self.isStop){
[self callMyAPI];
[NSThread sleepfortimeinterval : 6 ];
}
}
But I find out today that there is a way provided by Foundation library to write a run loop. So I can rewrite my code as below:
MyBackgroundThread(){
NSTimer *timer = [NSTimer timerWithTimeInterval:6 target:self selector:#selector(callMyAPI) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[timer release];
while (! self.isCancelled) {
BOOL ret = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
However, I don't know if these is a better way to do my job then my original one? If it is, why? and how can I test the difference in efficiency(or other property?) between this two ways?
Thanks!
I think it's generally unnecessary to create new run loop for timer. I'd suggest one of two approaches:
Schedule NSTimer on main run loop, but have the called method then dispatch the request to background queue.
Create dispatch timer scheduled to run on designated background dispatch queue. To do that, create dispatch timer property:
#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);
There are times that creating a new run loop is useful, but it seems unnecessary in this simple scenario.
Having said that, it probably doesn't make sense to use a timer for initiating a network every six seconds. Instead, you probably want to start the next request six seconds after the prior one finishes. For a variety of reasons, your server might not be able to respond within six seconds, and you don't want concurrent requests to build up in these scenarios (which can happen if your requests run asynchronously).
So, I'd be inclined that the completion block of callMyAPI to do something as simple as:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6.0 * NSEC_PER_SEC)), queue, ^{
<#code to issue next request#>
});
This obviates the need for timers (and custom run loops) entirely.
Finally, if you really need to detect system changes with that frequency, it might suggest a very different server architecture. For example, if you're polling every six seconds to see if something changed on the server, you might consider a sockets-based implementation or use push notifications. In both of those approaches, the server will tell the client apps when the significant event takes place, rather than the app behaving like Bart Simpson in the back seat of the car, constantly asking "are we there yet?"
The appropriate architecture is probably a function of with what frequency the server data is likely to be changing and what the client app requirements are.

GCD and Thread Wait

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while(!weakSelf.isAnotherThreadCompleted && [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
;
[weakSelf doSomething];
});
Is it correct to put async thread to wait another thread in this way?
You're going to stall the global queue corresponding to DISPATCH_QUEUE_PRIORITY_DEFAULT. If you dispatch something else to this queue, it won't execute until you're done waiting.
In addition, I don't think a run loop will exist in the thread corresponding to this dispatch queue, so you're going to create one by calling - currentRunLoop and since no source will be attached to it, it will return immediately with the value NO (or, there will be a random run loop, and the behavior will be completely unpredictable).
Why don't you just call a block at the end of your working thread? Or use only one mechanism (GCD, NSOperation, threads, run loops) and the synchronization that goes with it, instead of mixing them?

runUntilDate doesn't work on a background thread

sleep works well but runUntilDate doesn't work on a background thread. But why?
-(IBAction) onDecsriptionThreadB:(id)sender
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1)
{
NSLog(#"we are here");
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
//sleep(2);
}
});
}
If no input sources or timers are attached to the run loop, this method exits immediately;
If you want to use runUntilDate you must add timer or input sources. My correct version is:
while (1)
{
NSLog(#"we are here");
[NSTimer scheduledTimerWithTimeInterval:100 target:self selector:#selector(doFireTimer:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
//sleep(2);
}
Take a look at the question Difference in usage of function sleep() and [[NSRunLoop currentRunLoop] runUntilDate]
NSRunLoop is better because it allows the runloop to respond to events while you wait. If you just sleep your thread your app will block even if events arrive (like the network responses you are waiting for).
Also the documentation of NSRunLoop says that:
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: until the specified expiration date.
If you are using GCD, the purpose is to generally get away from doing complicated thread coding right. What is the bigger purpose of you trying to do this. May be your big picture context will help explain the problem better.

Resources