setKeepAliveTimeout and BackgroundTasks

valvoline picture valvoline · Jan 24, 2011 · Viewed 19.6k times · Source

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.

Answer

mattv123 picture mattv123 · Apr 18, 2013

This is an old thread, but may warrant an update.

As of iOS 6, this is the behavior I am seeing with the VoIP timer background methods as discussed in this thread:

  • The VoIP BackgroundMode is still strictly banned from AppStore apps via the App Review Process
  • The minimum KeepAlive time is 600 seconds; anything less than that will cause the handler to fail to install (and a warning is sent to NSLog)
  • Setting the keepAlive time to something significantly larger than 600 seconds will typically result in the handler being fired with a frequency of every time/2 interval. Bonus fact: This is consistent with the SIP REGISTER request, in which the recommended re-registration interval is .5*re-register time.
  • When your keepAlive handler is called, I have observed the following:
    • You get about 10 seconds of "foreground" execution time, in which remaining background time is infinite (as returned by backgroundTimeRemaining)
    • If you kicked off a beginBackgroundTask from within the keepAlive handler, I have observed that you get 60 seconds of background execution time (as returned by backgroundTimeRemaining). This is different than the 600 seconds you get when the user transitions from your app being active to backgrounded. I have not found any way to extend this time (without using other trick like location etc.)

Hope that helps!