Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to troubleshoot iOS background app fetch not working?

I am trying to get iOS background app fetch to work in my app. While testing in Xcode it works, when running on the device it doesn't!

  • My test device is running iOS 9.3.5 (my deployment target is 7.1)
  • I have enabled "Background fetch" under "Background modes" under "Capabilities" on the target in Xcode

enter image description here

In application:didFinishLaunchingWithOptions I have tried various intervals with setMinimumBackgroundFetchInterval, including UIApplicationBackgroundFetchIntervalMinimum

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {      // tell the system we want background fetch     //[application setMinimumBackgroundFetchInterval:3600]; // 60 minutes     [application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];     //[application setMinimumBackgroundFetchInterval:1800]; // 30 minutes      return YES; } 

I have implemented application:performFetchWithCompletionHandler

void (^fetchCompletionHandler)(UIBackgroundFetchResult); NSDate *fetchStart;  -(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {     fetchCompletionHandler = completionHandler;      fetchStart = [NSDate date];      [[NSUserDefaults standardUserDefaults] setObject:fetchStart forKey:kLastCheckedContentDate];     [[NSUserDefaults standardUserDefaults] synchronize];      [FeedParser parseFeedAtUrl:url withDelegate:self]; }   -(void)onParserFinished {     DDLogVerbose(@"AppDelegate/onParserFinished");      UIBackgroundFetchResult result = UIBackgroundFetchResultNoData;      NSDate *fetchEnd = [NSDate date];     NSTimeInterval timeElapsed = [fetchEnd timeIntervalSinceDate:fetchStart];     DDLogVerbose(@"Background Fetch Duration: %f seconds", timeElapsed);     if ([self.mostRecentContentDate compare:item.date] < 0) {         DDLogVerbose(@"got new content: %@", item.date);         self.mostRecentContentDate = item.date;         [self scheduleNotificationWithItem:item];         result = UIBackgroundFetchResultNewData;     }     else {         DDLogVerbose(@"no new content.");         UILocalNotification* localNotification = [[UILocalNotification alloc] init];         localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:60];         localNotification.alertBody = [NSString stringWithFormat:@"Checked for new posts in %f seconds", timeElapsed];         localNotification.timeZone = [NSTimeZone defaultTimeZone];         [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];     }      fetchCompletionHandler(result); } 
  • I have (successfully!) tested with the simulator and device using Xcode's Debug/SimulateBackgroundFetch

  • I have successfully tested with a new scheme as shown in another SO answer (https://stackoverflow.com/a/29923802/519030)

  • My tests show code executing in the performFetch method in about 0.3 seconds (so it's not taking a long time)
  • I have verified that the device has background refresh enabled within settings.
  • Of course, I've looked at the other SO questions hoping someone else experienced the same thing. :)

When running on the device and not connected to Xcode, my code is not executing. I've opened the app, closed the app (not killed the app!), waited hours and days. I have tried logging in the fetch handers, and also written code to send local notifications.

I once successfully saw my local notifications test on the device, and in fact iOS seemed to trigger the fetch three times, each about about fifteen minutes apart, but then it never occurred again.

I know the algorithm used to determine how frequently to allow the background fetch to occur is a mystery, but I would expect it to run at least occasionally within a span of days.

I am at a loss for what else to test, or how to troubleshoot why it seems to work in the simulator but not on the device.

Appreciate any advice!

like image 588
Jason Avatar asked Aug 29 '16 03:08

Jason


People also ask

Does background fetch work when app is terminated?

The Background Fetching will NOT happen in your app after the user has killed it in the multitasking UI. This is by design.

What is background fetch in iOS?

Background fetch is a new mode that lets your app appear always up-to-date with the latest information while minimizing the impact on battery. You could download feeds within fixed time intervals with this capability. To get started: 1- Check Background Fetch in capabilities screen in Xcode.

Can iOS app run in background?

No. Applications cannot run in the background for over 10 minutes, except for a few certain situations (VOIP, playing audio, etc.) An iOS app which plays audio will keep playing audio indefinitely in the background so long as the audio is playing.


1 Answers

Your problem is that you are returning from performFetchWithCompletionHandler before you call the completion handler, since the network fetch operation is occurring the in the background and you call the completion handler in your delegate method. Since iOS thinks you aren't playing by the rules it will deny your ability to use background fetch.

To fix the problem you need to call beginBackgroundTaskWithExpirationHandler and then end that task after you have called the completion handler.

Something like:

UIBackgroundTaskIdentifier backgroundTask  -(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {     fetchCompletionHandler = completionHandler;      fetchStart = [NSDate date];      self.backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^{         [application endBackgroundTask:self.backgroundUpdateTask];         self.backgroundTask = UIBackgroundTaskInvalid;     }];      [[NSUserDefaults standardUserDefaults] setObject:fetchStart forKey:kLastCheckedContentDate];      [FeedParser parseFeedAtUrl:url withDelegate:self]; }  -(void)onParserFinished {     DDLogVerbose(@"AppDelegate/onParserFinished");      UIBackgroundFetchResult result = UIBackgroundFetchResultNoData;      NSDate *fetchEnd = [NSDate date];     NSTimeInterval timeElapsed = [fetchEnd timeIntervalSinceDate:fetchStart];     DDLogVerbose(@"Background Fetch Duration: %f seconds", timeElapsed);     if ([self.mostRecentContentDate compare:item.date] < 0) {         DDLogVerbose(@"got new content: %@", item.date);         self.mostRecentContentDate = item.date;         [self scheduleNotificationWithItem:item];         result = UIBackgroundFetchResultNewData;     }     else {         DDLogVerbose(@"no new content.");         UILocalNotification* localNotification = [[UILocalNotification alloc] init];         localNotification.alertBody = [NSString stringWithFormat:@"Checked for new posts in %f seconds", timeElapsed];         [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];     }     fetchCompletionHandler(result);     [[UIApplication sharedApplication] application endBackgroundTask:self.backgroundUpdateTask];     self.backgroundTask = UIBackgroundTaskInvalid; } 

My test app using this approach has been executing a fetch every 15 minutes initially, but it becomes less frequent over time. Without the background task it exhibited the same issue you are seeing.

I found that setting the background fetch interval to something other than UIApplicationBackgroundFetchIntervalMinimum also helps. My test app is running with a background fetch interval of 3600 (one hour) and has been reliably triggering for several days now; even after a phone restart and not running the app again. The actual trigger interval is 2-3 hours however.

My sample app is here

like image 123
Paulw11 Avatar answered Oct 02 '22 07:10

Paulw11