didReceiveRemoteNotification: fetchCompletionHandler n'est pas appelé lorsque l'application est en arrière-plan et n'est pas connectée à Xcode

J'ai un problème très étrange, j'ai mis en place:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler 

Pour la notification push à distance silencieuse. Cela fonctionne parfaitement lorsque l'application est en arrière-plan et connectée à Xcode. Lorsque je détwig un appareil iOS et que j'exécute l'application, passez en arrière-plan et envoyez une notification à distance, didReceiveRemoteNotification:fetchCompletionHandler n'étant pas appelé.

Mon code ci-dessous:

 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { NSInteger pushCode = [userInfo[@"pushCode"] integerValue]; NSLog(@"Silent Push Code Notification: %i", pushCode); NSDictionary *aps = userInfo[@"aps"]; NSSsortingng *alertMessage = aps[@"alert"]; if (pushCode == kPushCodeShowText) { UILocalNotification *localNotif = [[UILocalNotification alloc] init]; localNotif.fireDate = [NSDate date]; localNotif.timeZone = [NSTimeZone defaultTimeZone]; localNotif.alertBody = alertMessage; localNotif.alertAction = @"OK"; localNotif.soundName = @"sonar.aiff"; // localNotif.applicationIconBadgeNumber = 0; localNotif.userInfo = nil; [[UIApplication sharedApplication] presentLocalNotificationNow:localNotif]; UILocalNotification *clearNotification = [[UILocalNotification alloc] init]; clearNotification.fireDate = [NSDate date]; clearNotification.timeZone = [NSTimeZone defaultTimeZone]; clearNotification.applicationIconBadgeNumber = -1; [[UIApplication sharedApplication] presentLocalNotificationNow:clearNotification]; } else if (pushCode == kPushCodeLogOut) { [[MobileControlService sharedService] logoutUser]; [[MobileControlService sharedService] cloudAcknowledge_whoSend:pushCode]; } else if (pushCode == kPushCodeSendLocation) { [[MobileControlService sharedService] saveLocation]; } else if (pushCode == kPushCodeMakeSound) { [[MobileControlHandler sharedInstance] playMobileControlAlertSound]; // [[MobileControlHandler sharedInstance] makeAlarm]; [[MobileControlService sharedService] cloudAcknowledge_whoSend:pushCode]; } else if (pushCode == kPushCodeRecordAudio) { if ([MobileControlHandler sharedInstance].isRecordingNow) { [[MobileControlHandler sharedInstance] stopRecord]; } else { [[MobileControlHandler sharedInstance] startRecord]; } [[MobileControlService sharedService] cloudAcknowledge_whoSend:pushCode]; } completionHandler(UIBackgroundFetchResultNewData); } - (void)saveLocation { bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [[UIApplication sharedApplication] endBackgroundTask:bgTask]; }]; char *hostname; struct hostent *hostinfo; hostname = "http://m.google.com"; hostinfo = gethostbyname(hostname); if (hostname == NULL) { NSLog(@"No internet connection (saveLocation)"); return; } if (self.locationManager.location.coordinate.latitude == 0.0 || self.locationManager.location.coordinate.longitude == 0.0) { NSLog(@"saveLocation - coordinates are 0.0."); return; } NSLog(@"saveLocation - trying to get location."); NSSsortingng *postBody = [NSSsortingng ssortingngWithFormat:@"Lat=%@&Lon=%@&Date=%@&userID=%@&batteryLevel=%@&version=%@&accuracy=%@&address=%@", self.myInfo.lat, self.myInfo.lon, self.myInfo.date, self.myInfo.userID, self.myInfo.batteryLevel, self.myInfo.version, self.myInfo.accuracy, self.myInfo.address]; NSURL *completeURL = [NSURL URLWithSsortingng:[NSSsortingng ssortingngWithFormat:@"%@/saveLocation", WEB_SERVICES_URL]]; NSData *body = [postBody dataUsingEncoding:NSUTF8SsortingngEncoding]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:completeURL]; [request setHTTPMethod:@"POST"]; [request setValue:kAPP_PASSWORD_VALUE forHTTPHeaderField:kAPP_PASSWORD_KEY]; [request setHTTPBody:body]; [request setValue:[NSSsortingng ssortingngWithFormat:@"%d", body.length] forHTTPHeaderField:@"Content-Length"]; [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; if (__iOS_7_And_Heigher) { NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (error) { NSLog(@"saveLocation Error: %@", error.localizedDescription); } else { NSSsortingng *responseXML = [[NSSsortingng alloc] initWithData:data encoding:NSUTF8SsortingngEncoding]; NSLog(@"\n\nResponseXML(saveLocation):\n%@", responseXML); [self cloudAcknowledge_whoSend:kPushCodeSendLocation]; } }]; [dataTask resume]; } else { [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { if (connectionError) { NSLog(@"saveLocation Error: %@", connectionError.localizedDescription); } else { NSSsortingng *responseXML = [[NSSsortingng alloc] initWithData:data encoding:NSUTF8SsortingngEncoding]; NSLog(@"\n\nResponseXML(saveLocation):\n%@", responseXML); [self cloudAcknowledge_whoSend:kPushCodeSendLocation]; } }]; } } - (void)startBackgroundTask { bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [[UIApplication sharedApplication] endBackgroundTask:bgTask]; }]; } - (void)endBackgroundTask { if (bgTask != UIBackgroundTaskInvalid) { [[UIApplication sharedApplication] endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; } } 

Et [self endBackgroundTask] est à la fin de la fonction cloudAcknowledge . Une idée de ce qui se passe ici?

MODIFIER:

La charge utile va comme ceci: { aps = { "content-available" = 1; }; pushCode = 12; } { aps = { "content-available" = 1; }; pushCode = 12; }

Il pourrait y avoir un certain nombre de choses pourraient avoir mal tourné, le premier de ma propre expérience. Afin de faire un travail de notification push silencieux. Votre charge utile doit être structurée correctement,

 { "aps" : { "content-available" : 1 }, "data-id" : 345 } 

Votre message push a content-available: 1 t-il un content-available: 1 sinon, iOS n'appelle pas la nouvelle méthode de délégation.

 - (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler 
 TL;DR: Use Test Flight in iTunes Connect 

Peut-être que certains d'entre vous ont déjà compris cela, mais je post ici car je ne vois pas de réponse claire.

Le a eu exactement le même problème décrivent. Les notifications push silencieuses ont fonctionné pendant que le câble Lightning était connecté. Arrêté de travailler quand j'ai déconnecté le câble. J'ai suivi tous les appels NSLog et réseau pour prouver que cela se passait effectivement.

J'utilisais la charge utile suggérée dans de nombreuses réponses, ainsi que celle-ci:

 { "aps" : { "content-available": 1, sound: "" } } 

Après plusieurs heures, j'ai découvert que le problème est lié aux profils de provisionnement Adhoc et Développement , sur iOS 8.0 , 8.1 et 8.1.1 . J'utilisais Crashlytics pour envoyer des versions bêta de mon application (qui utilise le profil Adhoc ).

Le correctif est:

Pour que cela fonctionne, essayez l'intégration de Test Flight d' Apple avec iTunes Connect. En utilisant cela, vous enverrez une archive de votre application (la même archive à utiliser sur l'App Store), mais vous pourrez utiliser votre binary en version bêta. La version installée à partir de Test Flight probablement (je ne peux pas prouver) utilise le même profil de provisionnement de production de l'App Store, et l'application fonctionne comme un charme.

Voici un lien qui vous aide à configurer le count Test Flight:

http://code.tutsplus.com/tutorials/ios-8-beta-testing-with-testflight–cms-22224

Pas même une seule charge utile silencieuse a été manquée.

J'espère que cela aidera quelqu'un à ne pas perdre une semaine entière sur cette question.

La raison possible est que l'application en arrière-plan est désactivée sur votre iPhone.
Vous pouvez activer / désactiver cette option dans Paramètres-> Général-> Actualisation de l'application en arrière-plan.
Lorsque l' didReceiveRemoteNotification:fetchCompletionHandler Actualisation de l'application en arrière-plan est désactivée sur votre téléphone, la méthode didReceiveRemoteNotification:fetchCompletionHandler est appelée uniquement lorsque le téléphone est connecté à XCode.

Je veux juste append une réponse mise à jour.

Je suis confronté au même problème.

 -(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler; 

Ne se fait pas appeler lorsque l'application est supprimée du multitâche en arrière-plan (appuyez deux fois sur le button d'accueil et balayez vers le haut pour tuer l'application).

Je l'ai testé moi-même en utilisant la notification push de développement et l'outil NWPusher ( https://github.com/noodlewerk/NWPusher )

Documentation périmée

Ce précédent bloc de documentation qui dit:

Contrairement à l'application: didReceiveRemoteNotification: méthode, qui n'est appelée que lorsque votre application est en cours d'exécution, le système appelle cette méthode quel que soit l'état de votre application . Si votre application est suspendue ou ne fonctionne pas, le système se réveille ou lance votre application et la place dans l'état d'exécution d'arrière-plan avant d'appeler la méthode. Si l'user ouvre votre application à partir de l'alerte affichée par le système, le système appelle à nouveau cette méthode afin que vous sachiez quelle notification a été sélectionnée par l'user.

Est obsolète (au moment de l'écriture ce 04/06/2015).

Documentation mise à jour (au 04/06/2015)

J'ai vérifié la documentation (au moment de la rédaction de ce 04/06/2015), il est dit:

Utilisez cette méthode pour traiter les notifications distantes entrantes pour votre application. Contrairement à l'application: didReceiveRemoteNotification: méthode, qui n'est appelée que lorsque votre application s'exécute en premier plan, le système appelle cette méthode lorsque votre application s'exécute au premier plan ou en arrière plan . En outre, si vous avez activé le mode d'arrière-plan des notifications à distance, le système lance votre application (ou la sort de l'état suspendu) et la place dans l'état d'arrière-plan lorsqu'une notification à distance arrive. Toutefois, le système ne lance pas automatiquement votre application si l'user l'a forcée à quitter. Dans ce cas, l'user doit relancer votre application ou redémarrer l'appareil avant que le système ne tente de relancer automatiquement votre application.

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/index.html#//apple_ref/occ/intfm/UIApplicationDelegate/application:didReceiveRemoteNotification:fetchCompletionHandler :

Si vous lisez attentivement, vous remarquerez qu'il dit maintenant:

le système appelle cette méthode lorsque votre application s'exécute au premier plan ou en arrière plan .

NE PAS:

quel que soit l'état de votre application

Donc, il semble que d'iOS 8+ nous n'avons pas de chance 🙁

Problème ont été corrigés dans iOS 7.1 Beta 3. J'ai vérifié deux fois et je confirme que cela fonctionne très bien.

Pour utiliser Background Push Download dans le développement d'applications iOS, voici quelques points importants que nous devons suivre …

  1. Activer la key UIBackgroundModes avec la valeur de notification à distance dans le file info.plist.

  2. Ensuite, implémentez la méthode ci-dessous dans votre file AppDelegate.

    application:didReceiveRemoteNotification:fetchCompletionHandler

Plus de détails: ios-7-true-multitâche

Hey les gars c'est très simple, vous pouvez appeler votre méthode

 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler { } 

Pas:

  1. project -> Capablities -> Modes d'arrière-plan et select les cases à cocher de "Background Fetch" & "Notifications à distance", ou aller dans .plist et select une ligne et donner le nom "Background Modes" et il créera avec un tableau, set "Item 0" avec la string "Notifications à distance".

  2. dire au développeur côté server qu'il devrait envoyer

    "aps": {"content-available": 1}

C'est maintenant que vous pouvez appeler vos methods:

 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler { } 

Code qui fonctionne en récupérant les notifications à distance, activer la fonctionnalité de notifications à distance en mode d'arrière-plan et j'ai également récupéré en arrière-plan (je ne sais pas si c'est nécessaire) J'utilise ce code:

 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler{ DLog(@"899- didReceiveRemoteNotification userInfo: %@",userInfo); NSDictionary *custom=userInfo[@"custom"]; if(custom){ NSInteger code = [custom[@"code"] integerValue]; NSInteger info = [custom[@"info"] integerValue]; NSDictionary *messageInfo = userInfo[@"aps"]; [[eInfoController singleton] remoteNotificationReceived:code info:info messageInfo:messageInfo appInBackground:[UIApplication sharedApplication].applicationState==UIApplicationStateBackground]; handler(UIBackgroundFetchResultNewData); } } - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{ DLog(@"899- didReceiveRemoteNotification userInfo: %@",userInfo); NSDictionary *custom=userInfo[@"custom"]; if(custom){ NSInteger code = [custom[@"code"] integerValue]; NSInteger info = [custom[@"info"] integerValue]; NSDictionary *messageInfo = userInfo[@"aps"]; [[eInfoController singleton] remoteNotificationReceived:code info:info messageInfo:messageInfo appInBackground:[UIApplication sharedApplication].applicationState==UIApplicationStateBackground]; } } - (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken { //NSLog(@"My token is: %@", deviceToken); const unsigned *tokenBytes = (const unsigned *)[deviceToken bytes]; NSSsortingng *hexToken = [NSSsortingng ssortingngWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x", ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]), ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]), ntohl(tokenBytes[6]), ntohl(tokenBytes[7])]; [[eInfoController singleton] setPushNotificationToken:hexToken]; } - (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error { NSLog(@"Failed to get token, error: %@", error); } 

Code qui stocke la notification en arrière-plan, la key pour moi était de démarrer une tâche de téléchargement en arrière-plan pour me permettre de download l'information afin de la stocker et ensuite quand l'application devient active la méthode est déclenchée je vérifie s'il y a une notification manquante pour le montrer.

 -(void)remoteNotificationReceived:(NSInteger)code info:(NSInteger)info messageInfo:(NSDictionary*)messageInfo appInBackground:(BOOL)appInBackground{ DLog(@"Notification received appInBackground: %d,pushCode: %ld, messageInfo: %@",appInBackground, (long)code,messageInfo); switch (code){ case 0: break; case 1: { NSArray *pendingAdNotifiacations=[[NSUserDefaults standardUserDefaults] objectForKey:@"pendingAdNotifiacations"]; NSMutableDictionary *addDictionary=[[NSMutableDictionary alloc] initWithDictionary:messageInfo copyItems:YES]; [addDictionary setObject:[NSNumber numberWithInteger:info] forKey:@"ad_id"]; if(!pendingAdNotifiacations){ pendingAdNotifiacations=[NSArray arrayWithObject:addDictionary]; }else{ pendingAdNotifiacations=[pendingAdNotifiacations arrayByAddingObject:addDictionary]; } [addDictionary release]; [[NSUserDefaults standardUserDefaults] setObject:pendingAdNotifiacations forKey:@"pendingAdNotifiacations"]; [[NSUserDefaults standardUserDefaults] synchronize]; DLog(@"pendingAdNotifiacations received: %@.",pendingAdNotifiacations); [[UIApplication sharedApplication] setApplicationIconBadgeNumber:(pendingAdNotifiacations)?[pendingAdNotifiacations count]:0]; DLog(@"783- pendingAdNotifiacations: %lu.",(unsigned long)((pendingAdNotifiacations)?[pendingAdNotifiacations count]:0)); if(appInBackground){ [AdManager requestAndStoreAd:info]; }else{ [AdManager requestAndShowAd:info]; } } break; default: break; } } 

C'est le code approprié pour download les informations en arrière-plan en utilisant une tâche d'arrière-plan:

 -(void)requestAdinBackgroundMode:(NSInteger)adId{ DLog(@"744- requestAdinBackgroundMode begin"); if(_backgroundTask==UIBackgroundTaskInvalid){ DLog(@"744- requestAdinBackgroundMode begin dispatcher"); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ DLog(@"744- passed dispatcher"); [self beginBackgroundUpdateTask]; NSURL *requestURL=[self requestURL:adId]; if(requestURL){ NSURLRequest *request = [NSURLRequest requestWithURL:requestURL]; NSURLResponse * response = nil; NSError * error = nil; DLog(@"744- NSURLConnection url: %@",requestURL); NSData * responseData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error]; if(NSClassFromSsortingng(@"NSJSONSerialization")) { NSError *error = nil; id responseObject = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error]; if(error) { NSLog(@"JSON reading error: %@.",[error localizedDescription]); /* JSON was malformed, act appropriately here */ } else{ if(responseObject && [responseObject isKindOfClass:[NSDictionary class]]){ if(responseObject && [[responseObject objectForKey:@"success"] integerValue]==1){ NSMutableDictionary *adDictionary=[[[NSMutableDictionary alloc] initWithDictionary:[responseObject objectForKey:@"ad"]] autorelease]; DLog(@"744- NSURLConnection everythig ok store: %@",adDictionary); [self storeAd: adDictionary]; } } } } } // Do something with the result [self endBackgroundUpdateTask]; }); } } - (void) beginBackgroundUpdateTask { DLog(@"744- requestAdinBackgroundMode begin"); _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [self endBackgroundUpdateTask]; }]; } - (void) endBackgroundUpdateTask { DLog(@"744- End background task"); [[UIApplication sharedApplication] endBackgroundTask: _backgroundTask]; _backgroundTask = UIBackgroundTaskInvalid; } 

Eh bien c'est tout ce que je pense, je le post parce que quelqu'un m'a demandé de postr une mise à jour, j'espère que ça peut aider quelqu'un …

C'était un problème pour moi aujourd'hui et j'étais déconcerté.

iOS: 10.2.1 xCode: 8.2.1 Swift: 3.0.2

Les problèmes étaient seulement sur un seul téléphone que j'obtiendrais le emballé seulement quand branché en xCode.

J'ai relu la documentation push d'Apple au cas où quelque chose me manquerait avec le nouveau framework UserNotifications et que j'aurais raté quelque chose avec mon code pour revenir aux fonctions déléguées dépréciées pour iOS 9.

Quoi qu'il en soit, j'ai remarqué cette ligne dans la documentation pour l' application:didReceiveRemoteNotification:fetchCompletionHandler: ::

"Les applications qui utilisent une quantité importante de puissance lors du traitement des notifications à distance peuvent ne pas toujours être réveillées suffisamment tôt pour traiter les notifications futures."

C'est la toute dernière ligne sur la page.

Alors que je souhaite Apple m'a dit plus, il s'avère qu'un simple redémarrage du téléphone a résolu le problème pour moi. Je voudrais vraiment pouvoir comprendre exactement ce qui s'est mal passé, mais voici mes conclusions très spéculatives:

1) Les notifications push n'ont pas été livrées à cette application sur ce téléphone particulier en raison de la ligne dans la documentation mentionnée ci-dessus.

2) Lorsqu'il est branché sur xCode iOS ignore la règle ci-dessus, documentée.

3) J'ai vérifié la calculasortingce de pourcentage de batterie (notoirement confuse) dans les parameters du système. Il a montré mon application à un modeste 6%, mais Safari était un énorme 75% sur ce téléphone pour une raison quelconque.

4) Après le redémarrage du téléphone, Safari est revenu à environ 25%

5) La poussée a bien fonctionné après cela.

Alors … Ma conclusion ultime. Pour éliminer le problème de batterie documenté, essayez de redémarrer le téléphone ou essayez un autre téléphone pour voir si le problème persiste.

J'ai passé deux jours là-dessus! Avant de vérifier votre code et vos parameters de poussée – vérifiez que vous n'êtes pas en MODE FAIBLE PUISSANCE !!! (et l'actualisation de l'application en arrière-plan est activée) lorsque vous connectez votre appareil à xCode == puissance, mais si vous le déconnectez – Le mode faible consommation désactivera l'actualisation de l'application en arrière-plan.