I've set up background fetch, using outline from NSScreencast episode #92.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
...
}
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
if ([[MyAppAPIClient manager] reachable] && [[Session manager] userSignedIn])
{
[[[Session manager] user] fetchBackgroundWithSuccess:^(NSURLSessionDataTask *task, NSDictionary *responseObject) {
completionHandler(UIBackgroundFetchResultNewData);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
completionHandler(UIBackgroundFetchResultFailed);
}];
}
else
completionHandler(UIBackgroundFetchResultNoData);
}
For the fetchBackgroundWithSuccess:failure
method, I'm using AFNetworking NSURLSessionDataTask
.
Unfortunately, sometimes I get the error
MyApp[3344] has active assertions beyond permitted time:
{(
<BKProcessAssertion: 0x178279c00> identifier: Background Content Fetching (1596) process: MyApp[3344] permittedBackgroundDuration: 30.000000 reason: backgroundContentFetching owner pid:16 preventSuspend preventThrottleDownUI preventIdleSleep preventSuspendOnSleep
)}
and it completely crashes my app, logs out my user, and all of the user's data is wiped clean.
Not sure how to fix this, but any direction would be much appreciated.
UPDATE:
The way I store data is using NSKeyedArchiver's archiveRootObject:toFile
and unarchiveObjectWithFile
. It keeps track if the user is logged in, their user_id, and other important info. This is all cached in memory using a singleton object of class User
.
archiveRootObject:toFile
is called during
applicationWillResignActive
applicationDidEnterBackground
applicationWillTerminate
applicationDidReceiveMemoryWarning
unarchiveObjectWithFile
is called as needed if the singleton is nil.
So logged out and wiped clean means the singleton is nil and unarchiveObjectWithFile
fails to retrieve any data.
Every background fetch has a time limit, which is I believe 30 seconds. Unlike a normal background task that can utilize an expiration handler to automatically clean up and do what you need to do when you run out of time, background fetches currently don't have that ability. One way you can handle this is perhaps start an NSTimer that is scheduled to go off something like 20-25 seconds into your background fetch, and have that timer call a function that handles if you haven't completed your background task to stop whatever you're doing and clean up everything so the app can go back to being backgrounded normally. From the docs:
https://developer.apple.com/library/ios/documentation/uikit/reference/uiapplicationdelegate_protocol/Reference/Reference.html#//apple_ref/occ/intfm/UIApplicationDelegate/application:performFetchWithCompletionHandler:
The bold part sounds like that's exactly what's happening. Either way, you need to get done what you're trying to get done within the 30 seconds, otherwise you need to cancel it or your app will be terminated.
This question is kind of old but maybe the following code can help some developers that are interested in a code snippet based on Mike´s answer. As Mike suggested I also use a NSTimer to cancel the background fetch when it takes too much time. In my background fetch I access my webservice with AFNetworking to grab some new data. If the time (I chose 25 seconds) is up I simply cancel all my open requests to the webservice in the NSOperationQueue. Here´s my code:
Hope this helps!