iOS background fetch time limit crash

2019-04-11 00:51发布

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

  1. applicationWillResignActive
  2. applicationDidEnterBackground
  3. applicationWillTerminate
  4. 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.

2条回答
▲ chillily
2楼-- · 2019-04-11 00:56

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:

When this method is called, your app has up to 30 seconds of wall-clock time to perform the download operation and call the specified completion handler block. In practice, your app should call the completion handler block as soon as possible after downloading the needed data. If you do not call the completion handler in time, your app is terminated.

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.

查看更多
Emotional °昔
3楼-- · 2019-04-11 01:23

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:

func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {

    // This Handler accesses my webservice using AFNetworking and creates NSOperationQueue to enqueue the requests
    backgroundSearchHandler = BackgroundFetchSearchHandler()

    let cancelTimer = NSTimer.scheduledTimerWithTimeInterval(25, target: self, selector: "cancelBackgroundFetch", userInfo: nil, repeats: false)

    backgroundSearchHandler!.findNewResultsForSavedSearches { (completed, newResultCount) -> Void in

        // This block is also called when the timer is over and the requests were cancelled

        // Stop the timer
        cancelTimer.invalidate()

        if !completed
        {
            completionHandler(.Failed)
            return
        }

        if newResultCount <= 0
        {
            completionHandler(.NoData)
            return
        }

        completionHandler(.NewData)
    }
}

func cancelBackgroundFetch()
{
    backgroundSearchHandler?.cancel()
    // This is calling cancelAllOperations() on the NSOperationQueue in my background search handler
}

Hope this helps!

查看更多
登录 后发表回答