setKeepAliveTimeout and BackgroundTasks

19,691

Solution 1

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!

Solution 2

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).

Solution 3

Just to update this behaviour for iOS7.

  • When your app first goes into the background you get 180 seconds of task time as reported by backgroundTimeRemaining. However, it stops responding at 5 seconds before this time runs out as reported by backgroundTimeRemaining.
  • When the keepAlive task fires then the backgroundTimeRemaining is 10 seconds of foreground followed by 60 seconds of background timer as reported by backgroundTimeRemaining. It also stops responding at 5 seconds before this time runs out as reported by backgroundTimeRemaining.

So on iOS7 you can get 65 seconds of processing time every 10 minutes.

Solution 4

It seems that you may be able to combine the keep alive timeout handler by piggy-backing a request for finite background task execution when you get called. This will allow you a full 10 minutes every time the VOIP keep alive handler gets called (vice the usual 10-30 seconds).

Still same problem as above -- in that you need the VOIP flag in your plist to submit, and Apple isn't likely to accept your application if you have that flag and are not actually a VOIP application, but for internal distribution (enterprise or otherwise), this solution should work fine for giving you background time.

In my tests, the 10 minute finite execution timer is reset every time the VOIP handler is called (whether or not the user has brought the application to the front since then), and this should mean that you could poll your server indefinitely while in the background once every 600 seconds (10 minutes) and the polling process can take up to 10 minutes before going to sleep (meaning nearly constant background operation if that is what you require).

Again, not really an option for the App Store unless you can convince them you are VOIP.

Share:
19,691
valvoline
Author by

valvoline

 advocate •📱dev • 📷 fuji • 🕹 retro • 🚴🏻‍♂️

Updated on July 27, 2022

Comments

  • valvoline
    valvoline almost 2 years

    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.

  • valvoline
    valvoline over 13 years
    many thanks for your reply. First of all, the reject is not an issue, since the app is a private app not for AppStore. Regarding the timeout of 30secs, i readed about it, but i cannot understand why the crash log tell me about a 600.00secs timeout, not a 30.00secs. Moreover, sometime the methods works, others they don't.
  • Jason Coco
    Jason Coco over 13 years
    @valvoline: The times it doesn't work, you're background task is obviously taking too long. The docs say 30 seconds, but perhaps that's the soft number and the OS really give you 10 minutes, I'm not sure. We know, though, that something is blocking at some point and your thing cannot complete, and since the VIOP was designed for a quick check, it's possible that the error handling from the OS is confused (like not calling your cancellation block or something).
  • Aviad Ben Dov
    Aviad Ben Dov over 12 years
    @jason: Do you have any specifics about what they require in this VOIP window? Does it state that anywhere public?
  • Patrick Horn
    Patrick Horn almost 12 years
    For those wondering about timeouts, RTFM here: developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Referen‌​ce/… It says there is a minimum of 600 seconds between keepalives, and you only have 10 seconds to perform the actions that you need, not 30.
  • Jason Coco
    Jason Coco almost 12 years
    @PatrickHorn Thanks for adding the link to the document. When this answer was written, the app was given 30 seconds to do whatever maintenance it needed on its voip socket. That was reduced to 10 seconds with iOS 5. Applications linked with the iOS 4 SDK are still given 30 seconds while new applications are given only 10.
  • reekris
    reekris over 11 years
    Are you resetting the execution timer manually in your ´setKeepAliveTimeout´ handler? Could you provide some example code for this? I'm in the same situation, using VOIP (not going to submit to AppStore). My execution timer is not getting reset automatically when app is woken by setKeepAliveTimeout. It seems though that I can reset it by creating a new background task the same way as in my ´applicationDidEnterBackground´.
  • BadPirate
    BadPirate over 11 years
    Yes, each time setKeepAliveTimeout is called I create a new finite background task (which seems to get a fresh 10 minutes to run).
  • gheese
    gheese over 11 years
    You need to bear this time limit in mind when debugging your app, I thought my code was crashing - the app was actually being closed