I've a big headache with the topic. I'm working on an application that needs to poll a webserver regularly, in order to check for new data. Based on the returned information, I wish to push a local notification to the user.
I know that this approach is slightly different from the one depicted by Apple, in which a remote server makes the work, pushing a remote notification, based on APNS. However, there are many reasons for which i cannot take this approach in consideration. One for all, is the user authentication mechanism. The remote server, for security reasons, cannot take into account the user credentials. All that i can do is to move the login and fetching core, to the client (iPhone).
I noticed that Apple offers an opportunity for applications to wake-up and keep opened a Socket connection (ie. a VoIP application).
So, I started investigate in this way. Added the required information in the plist, I'm able to "wake" my application, using something like this in my appDelegate:
[[UIApplication sharedApplication] setKeepAliveTimeout:1200 handler:^{
NSLog(@"startingKeepAliveTimeout");
[self contentViewLog:@"startingKeepAliveTimeout"];
MyPushOperation *op = [[MyPushOperation alloc] initWithNotificationFlag:0 andDataSource:nil];
[queue addOperation:op];
[op release];
}];
The NSOperation, then starts a background task using the following block code:
#pragma mark SyncRequests
-(void) main {
NSLog(@"startSyncRequest");
[self contentViewLog:@"startSyncRequest"];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
NSLog(@"exipiration handler triggered");
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
[self cancel];
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSMutableURLRequest *anURLRequest;
NSURLResponse *outResponse;
NSError *exitError;
NSString *username;
NSString *password;
NSLog(@"FirstLogin");
anURLRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:webserverLogin, username, password]]];
[anURLRequest setHTTPMethod:@"GET"];
[anURLRequest setTimeoutInterval:120.00];
[anURLRequest setCachePolicy:NSURLRequestReloadIgnoringCacheData];
exitError = nil;
NSData *tmpData = [NSURLConnection sendSynchronousRequest:anURLRequest returningResponse:&outResponse error:&exitError];
[anURLRequest setTimeoutInterval:120.00];
if(exitError != nil) { //somethings goes wrong
NSLog(@"somethings goes wrong");
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
[self cancel];
return;
}
//do some stuff with NSData and prompt the user with a UILocalNotification
NSLog(@"AlltasksCompleted");
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
[self cancel];
});
}
}
The above code seems to work (sometimes), but many other it crashes my application, with the following log information:
Exception Type: 00000020
Exception Codes: 0x8badf00d
Highlighted Thread: 3
Application Specific Information:
DemoBackApp[5977] has active assertions beyond permitted time:
{(
<SBProcessAssertion: 0xa9da0b0> identifier: UIKitBackgroundCompletionTask process: DemoBackApp[5977] permittedBackgroundDuration: 600.000000 reason: finishTask owner pid:5977 preventSuspend preventIdleSleep
)}
Elapsed total CPU time (seconds): 0.010 (user 0.010, system 0.000), 100% CPU
Elapsed application CPU time (seconds): 0.000, 0% CPU
For ones who ask, yes. I've tried the Async NSURLConnection approach, too. No matter. It crash the same, even if I use an async approach with timeout handler and didFinishLoading:WithError.
I'm stuck. Any hints are high appreciated.
When you call
-setKeepAliveTimeout:handler:
, you only get a maximum of 30 seconds to complete everything and suspend. You're not given the same background grace period as you'd be given when your application is first transitioned to the background. That's meant for finishing up long running tasks, shutting things down, etc.With the VOIP callback, you're just supposed to send whatever ping packet you need to send to your service to keep the network connection alive and not timed-out. After 30 seconds, regardless of starting new background tasks, if your application is still executing, you're gonna be terminated.
Also, it's important to note that if you're not actually a VOIP application or if you do anything during the VOIP callback window that's not related to keeping your network connections open, your app will be rejected from the app store. When you set any of the stay-active flags (VOIP, background music, navigation), they test it pretty rigorously to ensure that it only does what it's flagged to do while in the background. Doing any kind of HTTP GET request and waiting for some large data update to come back is almost guaranteed to get your app rejected.
EDIT: As noted by Patrick in the comments, the current amount of time the block is given to execute has been reduced from 30 seconds to 10 seconds with iOS 5. It's a good idea to keep an eye on these times whenever you re-link your application for a new version of the SDK, always at least quickly check the docs in case they've been updated (with iOS 6 coming out, this number may be tweaked again).