J'essaie de faire fonctionner la récupération de l'application d'arrière-plan iOS dans mon application. Pendant les tests dans Xcode, cela fonctionne, mais pas sur l'appareil!
Dans l'application: didFinishLaunchingWithOptions, j'ai essayé différents intervalles avec setMinimumBackgroundFetchInterval, y compris 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;
}
J'ai implémenté l'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);
}
J'ai (avec succès!) Testé avec le simulateur et l'appareil en utilisant Debug/SimulateBackgroundFetch de Xcode
J'ai testé avec succès avec un nouveau schéma comme indiqué dans une autre réponse SO ( https://stackoverflow.com/a/29923802/5190 )
Lors de l'exécution sur l'appareil et non connecté à Xcode, mon code ne s'exécute pas. J'ai ouvert l'application, fermé l'application (pas tué l'application!), Attendu des heures et des jours. J'ai essayé de me connecter aux gestionnaires d'extraction et j'ai également écrit du code pour envoyer des notifications locales.
Une fois, j'ai vu avec succès mon test de notifications locales sur l'appareil, et en fait, iOS a semblé déclencher la récupération trois fois, chacune à une quinzaine de minutes d'intervalle, mais cela ne s'est plus jamais produit.
Je sais que l'algorithme utilisé pour déterminer à quelle fréquence autoriser la récupération en arrière-plan est un mystère, mais je m'attendrais à ce qu'il s'exécute au moins occasionnellement en l'espace de quelques jours.
Je ne sais pas quoi tester d'autre, ni comment résoudre pourquoi cela semble fonctionner dans le simulateur mais pas sur l'appareil.
Appréciez tous les conseils!
Votre problème est que vous revenez de performFetchWithCompletionHandler
avant d'appeler le gestionnaire d'achèvement, car l'opération de récupération réseau se produit en arrière-plan et vous appelez le gestionnaire d'achèvement dans votre méthode déléguée. Étant donné qu'iOS pense que vous ne respectez pas les règles, cela vous empêchera d'utiliser la récupération en arrière-plan.
Pour résoudre le problème, vous devez appeler beginBackgroundTaskWithExpirationHandler
, puis terminer cette tâche après avoir appelé le gestionnaire d'achèvement.
Quelque chose comme:
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;
}
Mon application de test utilisant cette approche a d'abord exécuté une extraction toutes les 15 minutes, mais elle devient moins fréquente au fil du temps. Sans la tâche de fond, elle présentait le même problème que vous voyez.
J'ai trouvé que la définition de l'intervalle de récupération en arrière-plan sur autre chose que UIApplicationBackgroundFetchIntervalMinimum
aide également. Mon application de test fonctionne avec un intervalle de récupération en arrière-plan de 3600 (une heure) et se déclenche de manière fiable depuis plusieurs jours maintenant; même après un redémarrage du téléphone et ne pas réexécuter l'application. L'intervalle de déclenchement réel est cependant de 2-3 heures.
Mon exemple d'application est ici
Afin d'implémenter la récupération en arrière-plan, vous devez faire trois choses:
Fréquence de récupération en arrière-plan
La fréquence à laquelle notre application peut effectuer la récupération en arrière-plan est déterminée par le système et dépend de:
En d'autres termes, votre application est entièrement à la merci du système pour planifier une récupération en arrière-plan pour vous. De plus, chaque fois que votre application utilise la récupération en arrière-plan, elle dispose d'au plus 30 secondes pour terminer le processus.
Inclure dans votre AppDelegate (changez le code ci-dessous pour vos besoins de récupération)
-(void) application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(@"Background fetch started...");
//---do background fetch here---
// You have up to 30 seconds to perform the fetch
BOOL downloadSuccessful = YES;
if (downloadSuccessful) {
//---set the flag that data is successfully downloaded---
completionHandler(UIBackgroundFetchResultNewData);
} else {
//---set the flag that download is not successful---
completionHandler(UIBackgroundFetchResultFailed);
}
NSLog(@"Background fetch completed...");
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
return YES;
}
Pour vérifier si l'actualisation en arrière-plan est activée pour votre application, utilisez le code ci-dessous:
UIBackgroundRefreshStatus status = [[UIApplication sharedApplication] backgroundRefreshStatus];
switch (status) {
case UIBackgroundRefreshStatusAvailable:
// We can do background fetch! Let's do this!
break;
case UIBackgroundRefreshStatusDenied:
// The user has background fetch turned off. Too bad.
break;
case UIBackgroundRefreshStatusRestricted:
// Parental Controls, Enterprise Restrictions, Old Phones, Oh my!
break;
}