When should I apply Runloop to my program and why? - ios

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.

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 to use dispatch_get_main_queue

I have learnt one global rule in iOS -> never to block main thread.
However, several time I run into open source code snippets where this rule is violated.
Two such examples are follows :
The following function is taken from
https://github.com/piwik/piwik-sdk-ios/blob/master/PiwikTracker/PiwikTracker.m
- (void)startDispatchTimer {
// Run on main thread run loop
__weak typeof(self)weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf stopDispatchTimer];
// If dispatch interval is 0) {
// Run on timer
weakSelf.dispatchTimer = [NSTimer scheduledTimerWithTimeInterval:weakSelf.dispatchInterval
target:weakSelf
selector:#selector(dispatch:)
userInfo:nil
repeats:NO];
NSLog(#"Dispatch timer started with interval %f", weakSelf.dispatchInterval);
}
});
}
In the above code I have been trying to understand why is main thread required for timer object. Something like this is not UI related and still done on main thread.
Another example of this is in a famous networking library MKNetworkKit .
Where the following code is in start method of NSOperation.
https://github.com/MugunthKumar/MKNetworkKit/blob/master/MKNetworkKit/MKNetworkOperation.m
dispatch_async(dispatch_get_main_queue(), ^{
self.connection = [[NSURLConnection alloc] initWithRequest:self.request
delegate:self
startImmediately:NO];
[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
[self.connection start];
});
So my questions is why do people use main thread for doing no UI related operations and what benefit does it give. It may not freeze your app if you are not holding on to it but why take a chance.
Both examples are using NSRunLoop methods directly or indirectly. In those cases, you should call the methods from the thread which executes the target NSRunLoop. Thus you need dispatch_get_main_queue().
Take a look at the apple document about NSRunLoop https://developer.apple.com/library/ios/documentation/cocoa/reference/foundation/classes/nsrunloop_class/reference/reference.html
Warning: The NSRunLoop class is generally not considered to be thread-safe and its methods should only be called within the context of the current thread. You should never try to call the methods of an NSRunLoop object running in a different thread, as doing so might cause unexpected results.
By the way, NSRunLoop seems using CFRunLoop in Core Foundation, and Core Foundation was released under an open source license from Apple.
http://opensource.apple.com/source/CF/CF-855.17/CFRunLoop.c
It seems CFRunLoop is thread-safe (we can see a lot of __CFRunLoopLock and __CFRunLoopUnlock combination). But you would better obey the document anyway :)

Is it possible to use an NSTimer to wake a jailbroken iPhone from deep sleep?

Note: further down in the edits there's simple code that generates the problem without the full complexity of my original program.
I'm trying to code an alarm-clock app for jailbroken iOS. I have a UI set up as a standalone application for scheduling the alarms, which then saves the alarm information to disk. The save file is read by a launch daemon that's always running, which deals with actually scheduling the alarms.
I'm scheduling the alarms as so (EDIT: in the daemon) (NSDate *fireDate is calculated earlier):
NSTimer *singleTimer = [[NSTimer alloc] initWithFireDate:fireDate
interval:0
target:self
selector:#selector(soundAlarm:)
userInfo:alarm
repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:singleTimer
forMode:NSRunLoopCommonModes];
[self.timers addObject:singleTimer];
[singleTimer release];
EDIT: the above code runs in a method called createTimers, which gets called by reloadData. reloadData reads information about the timers from the shared save file, and it gets called in AMMQRDaemonManager's init function, as well as whenever the manager gets a notification (with notify_post) that the UI app has updated the save file.
The soundAlarm: method (EDIT: also in the daemon) is:
- (void)soundAlarm:(NSTimer *)theTimer {
NSLog(#"qralarmdaemon: sounding alarm");
extern CFStringRef kCFUserNotificationAlertTopMostKey;
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(dict, kCFUserNotificationAlertTopMostKey, kCFBooleanTrue);
CFDictionaryAddValue(dict, kCFUserNotificationAlertHeaderKey, CFSTR("Title"));
CFDictionaryAddValue(dict,kCFUserNotificationDefaultButtonTitleKey, CFSTR("OK"));
SInt32 err = 0;
CFUserNotificationRef notif = CFUserNotificationCreate(NULL,
0, kCFUserNotificationPlainAlertLevel, &err, dict);
CFOptionFlags response;
if((err) || (CFUserNotificationReceiveResponse(notif, 0, &response))) {
// do stuff
} else if((response & 0x3) == kCFUserNotificationDefaultResponse) {
// do stuff
}
CFRelease(dict);
CFRelease(notif);
// Do some other stuff
}
This works great, and shows the alert whether the phone is unlocked or locked. But if the phone is locked for a sufficient period of time to enter deep sleep then the timer just fails to fire.
I don't need it to necessarily turn the screen on (though that would be nice) since I'll also be playing sound in addition to displaying the alert, but I do need the timer to fire so that I know when to start the sound.
Any ideas?
EDIT: Here is the main function for the daemon.
int main(int argc, char **argv, char **envp) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"qralarmdaemon: launched");
AMMQRDaemonManager *manager = [[AMMQRDaemonManager alloc] init];
NSTimer *keepRunningTimer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture]
interval:1000
target:manager
selector:#selector(keepRunning:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:keepRunningTimer
forMode:NSRunLoopCommonModes];
// Execute run loop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop run];
[manager release];
NSLog(#"qralarmdaemon: exiting");
[pool release];
return 0;
}
(Not included is the code that registers for notifications from the main app to know when to read in the save file, etc, but I don't think that's relevant).
EDIT (again): I've added a timer to the run loop that fires at [NSDate distantFuture]. This seems to preserve the timers longer (a timer scheduled 1 min 45 secs after the phone was locked went off, and woke up the phone) but not indefinitely (a timer scheduled 7 min, 30 seconds after the phone was locked did not go off).
EDIT: I've constructed the following toy example that illustrates the problem, without having to worry about interactions with other parts of my code.
I compiled this code, SSH'd in, and ran it, then locked my phone. If I change the dateByAddingTimeInterval:480 to dateByAddingTimeInterval:30, I get the following output:
2013-03-31 12:21:25.555 daemontimertest[6160:707] daemon-timer-test: launched
2013-03-31 12:21:56.265 daemontimertest[6160:707] daemon-timer-test: timer fired
But when it's set to 480, I wait more than 8 minutes and only see the first line:
2013-03-31 12:08:09.331 daemontimertest[6049:707] daemon-timer-test: launched
main.m:
#import "MyClass.h"
int main(int argc, char **argv, char **envp) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"daemon-timer-test: launched");
MyClass *obj = [[MyClass alloc] init];
NSTimer *singleTimer = [[NSTimer alloc] initWithFireDate:[[NSDate date] dateByAddingTimeInterval:480]
interval:0
target:obj
selector:#selector(fireTimer:)
userInfo:nil
repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:singleTimer
forMode:NSRunLoopCommonModes];
// Execute run loop
[[NSRunLoop currentRunLoop] run];
[pool release];
return 0;
}
MyClass.m:
#import "MyClass.h"
#implementation MyClass
- (void)fireTimer:(NSTimer *)theTimer {
NSLog(#"daemon-timer-test: timer fired");
}
#end
EDIT (3/31/13 5:50 EDT): I've added the following code the toy app code to incorporate Nate's suggestion of using GCD's dispatch_after functionality, but it appears subject to the same time constraints. As an additional note, the main UI app is installed in /Applications and the daemon is installed in /usr/bin.
double delayInSeconds = 10.0;
NSLog(#"daemon-timer-test: delay is %f",delayInSeconds);
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"daemon-timer-test: time has passed.");
});
EDIT (3/31 5:54 PM): Another quick note. The following lines show up (not consecutively) in syslog right before it appears to go into deep sleep and there are no more messages before I wake the phone up. I've selected the ones that look like they may be relevant; the last message is the very last one sent to syslog before deep sleep.
Mar 31 17:34:23 Andrew-MacKie-Masons-iPhone lockdownd[50]: 002c1000 -[hostWatcher handleSleepNotification:service:messageArgument:]: <hostWatcher: 0x1cd59890> [CC535EDB-0413-4E5E-A844-4DA035E7217C 169.254.2.141:54757] [fd=13]: kIOMessageCanSystemSleep
Mar 31 17:34:23 Andrew-MacKie-Masons-iPhone lockdownd[50]: 002c1000 -[hostWatcher handleSleepNotification:service:messageArgument:]: <hostWatcher: 0x1cd59890> [CC535EDB-0413-4E5E-A844-4DA035E7217C 169.254.2.141:54757] [fd=13]: kIOMessageSystemWillSleep
...
Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone lockdownd[50]: 00343000 __63-[hostWatcher handleSleepNotification:service:messageArgument:]_block_invoke_0: Allowing Sleep
Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone powerd[42]: PM scheduled RTC wake event: WakeImmediate inDelta=645.40
Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone powerd[42]: Idle Sleep Sleep: Using BATT (Charge:76%)
...
Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone kernel[0]: en0::stopOutputQueues
...
Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone kernel[0]: pmu wake events: menu
Short Answer
Yes, it's possible (I've done it).
I tried a few different ways, and I was not able to get my daemon/NSTimer to fail in the way you're describing. However, I haven't seen all the files/code that defines your app, so there's at least one more thing I'm concerned about.
Keeping the Daemon Alive
If you look in the Apple docs for NSRunLoop run:
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.
In the code you show for you daemon's main program, you don't (directly) create any timers. Of course, I don't know what you do in [[AMMQRDaemonManager alloc] init], so maybe I'm wrong. You then use:
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop run];
to start the run loop. The problem is, if there are no timers at this point, I'm not sure your daemon is going to stay alive. If you look at the second paragraph above, it also indicates that it might stay alive, so maybe that's why I don't see my daemon die when I attempt to use your code.
Your comment says that you see the daemon process alive, when the alarm should go off. However, I'm wondering if maybe your daemon process did die, and then was restarted. Maybe you could also show us the .plist file you use for your Launch Daemon (that goes in /System/Library/LaunchDaemons).
One quick experiment, might be to not start your daemon automatically. Just uninstall the plist file from the LaunchDaemons folder, and make sure you kill the process. Then, start it manually from the command line, ssh'd into the phone:
$ /Applications/MyApp.app/MyDaemon
then, watch the command line. You'll see if it dies or not, and since it's not actually being run by launchd, it won't get restarted if it does die.
Solution?
If it turns out that you do have problems with it dying, then I would try adding a timer that always starts when you daemon does. If you look at my other example, or Chris Alvares' daemon tutorial, it shows this. In the daemon main(), you set one NSTimer to fire a run: method. In that run: method, you could use a while loop and a sleep() call. Or just schedule the timer to repeat at some slow interval.
I'm also not sure how your entire app works. Is it only a tool for scheduling (NSTimer) alarms? If so, it's possible that at any time, there might be no alarms set. Maybe another solution, instead of using the UIApplication to notify_post() to communicate a new timer to the daemon, you could configure the daemon to simply watch a data file. The UIApplication would write out the data file, whenever there is a new timer. Then, iOS could wake your daemon to schedule the NSTimer.
Anyway, this may be a separate issue from your original problem, but it also might be a more efficient way to build an alarm clock daemon, since it doesn't really need to run if there's no alarms active.
Post more if these ideas don't help you fix it (the body of [AMMQRDaemonManager init] might help).
Update
Two more suggestions:
make sure your app (daemon and UI) are installed in /Applications. This is the normal location for jailbreak apps, but I just wanted to make sure you weren't installing it in the sandbox area.
try replacing your NSTimer implementation (for the alarms, you can leave the main() daemon keepalive timer as is) with GCD blocks:
// you have used notify_post() to tell the daemon to schedule a new alarm:
double delayInSeconds = 1000.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// put timer expiration code here
});
Update II
I also noticed that in your original alarm: callback, you use CFUserNotificationReceiveResponse() with an infinite timeout. That means that if the user doesn't dismiss the popup, the timer callback won't complete, and I believe that means that no subsequently scheduled timer callbacks can fire. Probably, you should put all the CFUserNotification code into its own method (e.g. showPopup), and then have your timer callback like so:
- (void)soundAlarm:(NSTimer *)theTimer {
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self showPopup];
});
}
Then, there's the main program (in the code you put on Dropbox). I would recommend changing your main timer (that you call directly from main()) to be a repeating timer, with a relatively small interval, instead of using a fire date with distantFuture. If you want, you can do nothing in it. It's just a heartbeat.
main.m:
NSTimer *singleTimer = [[NSTimer alloc] initWithFireDate:[NSDate date]
interval:5*60 // 5 minutes
target:obj
selector:#selector(heartbeat:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:singleTimer
forMode:NSRunLoopCommonModes];
MyClass.m:
- (void)heartbeat:(NSTimer *)theTimer {
NSLog(#"daemon-timer-test: heartbeat timer fired");
}
My last comment is that I don't use syslogd. I'm wondering if any of your tests are failing, not because timers aren't running, but because NSLog statements aren't showing up in your log file. I've done all tests where I actually run the daemon executable at the command line, ssh'd into the phone, and I just watch the console for NSLog output. Take logging out of the list of possible failure points ...
I've worked out a method that works for me. As per my long exchange with Nate (and I definitely wouldn't have been able to work out what was going on without his help), this seems to happen automatically on some systems, but not on others. The problem on my phone seemed to be that powerd was putting the phone into some sort of deep sleep that paused the NSTimers and didn't allow them to fire properly.
Rather than disabling deep sleep (which I suspect has negative power implications) I scheduled a power event:
NSDate *wakeTime = [[NSDate date] dateByAddingTimeInterval:(delayInSeconds - 10)];
int reply = IOPMSchedulePowerEvent((CFDateRef)wakeTime, CFSTR("com.amm.daemontimertest"), CFSTR(kIOPMAutoWake));
This successfully wakes the phone 10 seconds before the alarm is supposed to go off. (The interval isn't precise. I wanted it to be short enough that the phone wouldn't go back to sleep, but long enough that if the phone takes a moment to wake up the timer can still go at the right time. I'll probably shorten it to just 3 or 4 seconds.
The remaining problem is that the NSTimer for the alarm itself won't update automatically, and so it'll be late by whatever period the phone was asleep for. To fix this you can cancel and reschedule the NSTimer whenever the phone wakes up. I did this by registering for a notification that the power management system posts whenever the power state changes:
int status, notifyToken;
status = notify_register_dispatch("com.apple.powermanagement.systempowerstate",
&notifyToken,
dispatch_get_main_queue(), ^(int t) {
// do stuff to cancel currently running timer and schedule a new one here
});
The inefficiency here is that the notification is posted both on sleeps and wakes, but I haven't been able to find an alternative yet.
I hope this is helpful to anyone else who was struggling with this issue.

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