objc.io

Avant iOS 7, les développeurs étaient assez limités dans ce qu’ils pouvaient faire lorsque leurs applications quittaient le premier plan. Mis à part la VOIP et les fonctionnalités basées sur la localisation, la seule façon d’exécuter du code en arrière-plan était d’utiliser des tâches en arrière-plan, limitées à quelques minutes. Si vous vouliez télécharger une grande vidéo pour une visualisation hors ligne ou sauvegarder les photos d’un utilisateur sur votre serveur, vous ne pouviez terminer qu’une partie du travail.

iOS 7 ajoute deux nouvelles API pour mettre à jour l’interface utilisateur et le contenu de votre application en arrière-plan. La première, la récupération en arrière-plan, vous permet de récupérer du nouveau contenu du réseau à intervalles réguliers. La seconde, les notifications à distance, est une nouvelle fonctionnalité exploitant les notifications Push pour avertir une application lorsqu’un événement s’est produit. Ces deux nouveaux mécanismes vous aident à maintenir l’interface de votre application à jour et peuvent planifier le travail sur le nouveau service de transfert en arrière-plan, qui vous permet d’effectuer des transferts réseau hors processus (téléchargements et téléchargements).

La récupération d’arrière-plan et les notifications à distance sont de simples crochets de délégué d’application avec 30 secondes d’horloge murale pour effectuer le travail avant la suspension de votre application. Ils ne sont pas destinés à un travail intensif du processeur ou à des tâches de longue durée, mais plutôt à la mise en file d’attente de demandes de mise en réseau de longue durée, comme un téléchargement de film volumineux, ou à des mises à jour rapides du contenu.

Du point de vue de l’utilisateur, le seul changement évident au multitâche est le nouveau sélecteur d’application, qui affiche un instantané de l’interface utilisateur de chaque application telle qu’elle était lorsqu’elle a quitté le premier plan. Mais il y a une raison pour afficher les instantanés : vous pouvez désormais mettre à jour l’instantané de votre application après avoir terminé le travail en arrière–plan, en affichant un aperçu du nouveau contenu. Les applications de réseaux sociaux, d’actualités ou de météo peuvent désormais afficher le dernier contenu sans que l’utilisateur ait à ouvrir l’application. Nous verrons comment mettre à jour l’instantané plus tard.

Récupération d’arrière-plan

La récupération d’arrière-plan est une sorte de mécanisme d’interrogation intelligent qui fonctionne mieux pour les applications qui ont des mises à jour de contenu fréquentes, comme les applications de réseautage social, d’actualités ou de météo. Le système réveille l’application en fonction du comportement de l’utilisateur et vise à déclencher des récupérations en arrière-plan avant que l’utilisateur ne lance l’application. Par exemple, si l’utilisateur utilise toujours une application à 13 heures, le système apprend et s’adapte, effectuant des récupérations avant les périodes d’utilisation. Les récupérations en arrière-plan sont fusionnées entre les applications par la radio de l’appareil afin de réduire l’utilisation de la batterie, et si vous signalez que de nouvelles données n’étaient pas disponibles lors d’une récupération, iOS peut s’adapter, en utilisant ces informations pour éviter les récupérations en période de silence.

La première étape pour activer la récupération en arrière-plan consiste à spécifier que vous utiliserez la fonctionnalité de la clé UIBackgroundModes dans votre liste d’informations. La façon la plus simple de le faire est d’utiliser le nouvel onglet Capacités dans l’éditeur de projet de Xcode 5, qui comprend une section Modes d’arrière-plan pour une configuration facile des options multitâches.

Vous pouvez également modifier la clé manuellement:

<key>UIBackgroundModes</key><array> <string>fetch</string></array>

Ensuite, dites à iOS à quelle fréquence vous souhaitez récupérer:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ ; return YES;}

L’intervalle de récupération par défaut n’est jamais, vous devrez donc définir un intervalle de temps ou l’application ne sera jamais appelée en arrière-plan. La valeur UIApplicationBackgroundFetchIntervalMinimum demande au système de gérer le réveil de votre application, aussi souvent que possible, mais vous devez spécifier votre propre intervalle de temps si cela n’est pas nécessaire. Par exemple, une application météo peut uniquement mettre à jour les conditions toutes les heures. iOS attendra au moins l’intervalle de temps spécifié entre les récupérations en arrière-plan.

Si votre application permet à un utilisateur de se déconnecter et que vous savez qu’il n’y aura pas de nouvelles données, vous pouvez définir minimumBackgroundFetchInterval à UIApplicationBackgroundFetchIntervalNever pour être un bon citoyen et conserver les ressources.

La dernière étape consiste à implémenter la méthode suivante dans votre délégué d’application:

- (void) application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{ NSURLSessionConfiguration *sessionConfiguration = ; NSURLSession *session = ; NSURL *url = initWithString:@"http://yourserver.com/data.json"]; NSURLSessionDataTask *task = ; // Start the task ;}

C’est là que vous pouvez effectuer le travail lorsque vous êtes réveillé par le système. N’oubliez pas que vous n’avez que 30 secondes pour déterminer si un nouveau contenu est disponible, pour le traiter et pour mettre à jour votre interface utilisateur. Cela devrait être assez de temps pour récupérer des données du réseau et pour récupérer quelques vignettes pour votre interface utilisateur, mais pas beaucoup plus. Lorsque vos demandes réseau sont terminées et que votre interface utilisateur a été mise à jour, vous devez appeler le gestionnaire de complétion.

Le gestionnaire de complétion a deux objectifs. Tout d’abord, le système mesure la puissance utilisée par votre processus et enregistre si de nouvelles données étaient disponibles en fonction de l’argument UIBackgroundFetchResult que vous avez passé. Deuxièmement, lorsque vous appelez le gestionnaire de complétion, un instantané de votre interface utilisateur est pris et le sélecteur d’application est mis à jour. L’utilisateur verra le nouveau contenu lorsqu’il changera d’application. Ce comportement de snapshotting du gestionnaire de complétion est commun à tous les gestionnaires de complétion des nouvelles API multitâches.

Dans une application du monde réel, vous devez transmettre le completionHandler aux sous-composants de votre application et l’appeler lorsque vous avez traité des données et mis à jour votre interface utilisateur.

À ce stade, vous vous demandez peut-être comment iOS peut capturer l’interface utilisateur de votre application lorsqu’elle s’exécute en arrière-plan et comment le cycle de vie de l’application fonctionne avec la récupération en arrière-plan. Si votre application est actuellement suspendue, le système la réveillera avant d’appeler application: performFetchWithCompletionHandler:. Si votre application n’est pas en cours d’exécution, le système la lancera en appelant les méthodes déléguées habituelles, y compris application: didFinishLaunchingWithOptions:. Vous pouvez le considérer comme l’application fonctionnant exactement de la même manière que si l’utilisateur l’avait lancée depuis Springboard, sauf que l’interface utilisateur est invisible, rendue hors écran.

Dans la plupart des cas, vous effectuerez le même travail lorsque l’application se lancera en arrière-plan que vous le feriez au premier plan, mais vous pouvez détecter les lancements en arrière-plan en regardant la propriété applicationState de UIApplication:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ NSLog(@"Launched in background %d", UIApplicationStateBackground == application.applicationState); return YES;}

Tester la récupération d’arrière-plan

Il existe deux façons de simuler une récupération d’arrière-plan. La méthode la plus simple consiste à exécuter votre application à partir de Xcode et à cliquer sur Simuler la récupération d’arrière-plan dans le menu de débogage de Xcode pendant l’exécution de votre application.

Vous pouvez également utiliser un schéma pour modifier la façon dont Xcode exécute votre application. Sous l’élément de menu Xcode Produit, choisissez Schéma, puis Gérer les schémas. À partir de là, modifiez ou ajoutez un nouveau schéma et cochez la case Lancer en raison d’un événement de récupération en arrière-plan, comme indiqué ci-dessous.

Notifications à distance

Les notifications à distance vous permettent d’avertir votre application lorsque des événements importants se produisent. Vous avez peut-être de nouveaux messages instantanés à envoyer, des alertes de dernière minute à envoyer ou le dernier épisode de l’émission de télévision préférée de votre utilisateur prêt à être téléchargé pour être visionné hors ligne. Les notifications à distance sont idéales pour les contenus sporadiques mais immédiatement importants, où le délai entre les récupérations en arrière-plan peut ne pas être acceptable. Les notifications à distance peuvent également être beaucoup plus efficaces que la récupération en arrière-plan, car votre application ne se lance que lorsque cela est nécessaire.

Une notification à distance n’est vraiment qu’une notification Push normale avec l’indicateur content-available défini. Vous pouvez envoyer un message push avec un message d’alerte informant l’utilisateur que quelque chose s’est passé, pendant que vous mettez à jour l’interface utilisateur en arrière-plan. Mais les notifications à distance peuvent également être silencieuses, ne contenant aucun message d’alerte ou son, utilisées uniquement pour mettre à jour l’interface de votre application ou déclencher un travail en arrière-plan. Vous pouvez ensuite publier une notification locale lorsque vous avez terminé de télécharger ou de traiter le nouveau contenu.

Les notifications push silencieuses sont limitées, alors n’ayez pas peur d’en envoyer autant que votre application en a besoin. iOS et les serveurs APNS contrôleront la fréquence à laquelle ils sont livrés, et vous n’aurez pas de problèmes pour en envoyer trop. Si vos notifications push sont limitées, elles peuvent être retardées jusqu’à la prochaine fois que l’appareil envoie un paquet de maintien en vie ou reçoit une autre notification.

Envoi de notifications à distance

Pour envoyer une notification à distance, définissez l’indicateur contenu disponible dans une charge utile de notification push. L’indicateur contenu disponible est la même clé utilisée pour notifier les applications en kiosque, de sorte que la plupart des scripts push et des bibliothèques prennent déjà en charge les notifications à distance. Lorsque vous envoyez une notification à distance, vous pouvez également inclure certaines données dans la charge utile de notification, afin que votre application puisse référencer l’événement. Cela pourrait vous faire économiser quelques demandes de mise en réseau et augmenter la réactivité de votre application.

Je recommande d’utiliser l’utilitaire Houston de Nomad CLI pour envoyer des messages push pendant le développement, mais vous pouvez utiliser votre bibliothèque ou votre script préféré.

Vous pouvez installer Houston dans le cadre de la gemme ruby nomad-cli:

gem install nomad-cli

Puis envoyez une notification avec l’utilitaire apn inclus dans Nomad

# Send a Push Notification to your Deviceapn push <device token> -c /path/to/key-cert.pem -n -d content-id=42

Ici, l’indicateur -n spécifie que la clé de contenu disponible doit être incluse et -d nous permet d’ajouter nos propres clés de données à la charge utile.

La charge utile de notification résultante ressemble à ceci:

{ "aps" : { "content-available" : 1 }, "content-id" : 42}

iOS 7 ajoute une nouvelle méthode de délégué d’application, appelée lorsqu’une notification push avec la clé content-available est reçue:

- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{ NSLog(@"Remote Notification userInfo is %@", userInfo); NSNumber *contentID = userInfo; // Do something with the content ID completionHandler(UIBackgroundFetchResultNewData);}

Encore une fois, l’application est lancée en arrière-plan et dispose de 30 secondes pour récupérer le nouveau contenu et mettre à jour son interface utilisateur, avant d’appeler le gestionnaire de complétion. Nous pourrions effectuer une demande réseau rapide comme nous l’avons fait dans l’exemple de récupération en arrière-plan, mais utilisons le puissant nouveau service de transfert en arrière-plan pour mettre en file d’attente une grande tâche de téléchargement et voir comment nous pouvons mettre à jour notre interface utilisateur une fois terminée.

NSURLSession et service de transfert d’arrière-plan

Alors que NSURLSession est une nouvelle classe dans iOS 7, il fait également référence à la nouvelle technologie de la mise en réseau de base. Destinés à remplacer NSURLConnection, les concepts et classes familiers tels que NSURL, NSURLRequest et NSURLResponse sont préservés. Vous travaillerez avec le remplaçant de NSURLConnection, NSURLSessionTask, pour effectuer des requêtes réseau et gérer leurs réponses. Il existe trois types de tâches de session – données, téléchargement et téléchargement – qui ajoutent chacune du sucre syntaxique à NSURLSessionTask, vous devez donc utiliser celle qui convient à votre cas d’utilisation.

Un NSURLSession coordonne un ou plusieurs de ces NSURLSessionTask s et se comporte selon le NSURLSessionConfiguration avec lequel il a été créé. Vous pouvez créer plusieurs NSURLSession pour regrouper les tâches associées avec la même configuration. Pour interagir avec le Service de transfert en arrière-plan, vous allez créer une configuration de session à l’aide de . Les tâches ajoutées à une session d’arrière-plan sont exécutées dans un processus externe et continuent même si votre application est suspendue, se bloque ou est tuée.

NSURLSessionConfiguration vous permet de définir des en-têtes HTTP par défaut, de configurer des stratégies de cache, de restreindre l’utilisation du réseau cellulaire, etc. Une option est l’indicateur discretionary, qui permet au système de planifier des tâches pour des performances optimales. Cela signifie que vos transferts ne passeront par Wifi que lorsque l’appareil est suffisamment alimenté. Si la batterie est faible ou si seule une connexion cellulaire est disponible, votre tâche ne s’exécutera pas. L’indicateur discretionary n’a d’effet que si l’objet de configuration de session a été construit en appelant la méthode backgroundSessionConfiguration: et si le transfert d’arrière-plan est lancé alors que votre application est au premier plan. Si le transfert est lancé à partir de l’arrière-plan, le transfert s’exécutera toujours en mode discrétionnaire.

Maintenant que nous en savons un peu plus sur NSURLSession, et sur le fonctionnement d’une session en arrière-plan, revenons à notre exemple de notification à distance et ajoutons du code pour mettre en file d’attente un téléchargement sur le service de transfert en arrière-plan. Une fois le téléchargement terminé, nous informerons l’utilisateur que le fichier est disponible.

NSURLSessionDownloadTask

Tout d’abord, gérons une Notification à distance et mettons en file d’attente un NSURLSessionDownloadTask sur le service de transfert en arrière-plan. Dans backgroundURLSession, nous créons un NURLSession avec une configuration de session en arrière-plan et ajoutons notre délégué d’application en tant que délégué de session. La documentation déconseille d’instancier plusieurs sessions avec le même identifiant, nous utilisons donc dispatch_once pour éviter les problèmes potentiels:

- (NSURLSession *)backgroundURLSession{ static NSURLSession *session = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSString *identifier = @"io.objc.backgroundTransferExample"; NSURLSessionConfiguration* sessionConfig = ; session = ]; }); return session;}- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{ NSLog(@"Received remote notification with userInfo %@", userInfo); NSNumber *contentID = userInfo; NSString *downloadURLString = ]; NSURL* downloadURL = ; NSURLRequest *request = ; NSURLSessionDownloadTask *task = downloadTaskWithRequest:request]; task.taskDescription = ]; ; completionHandler(UIBackgroundFetchResultNewData);}

Nous créons une tâche de téléchargement à l’aide de la méthode de classe NSURLSession, configurons sa requête et fournissons une description pour une utilisation ultérieure. Vous devez vous rappeler d’appeler pour démarrer réellement la tâche, car toutes les tâches de session commencent à l’état suspendu.

Maintenant, nous devons implémenter les méthodes NSURLSessionDownloadDelegate pour recevoir des rappels une fois le téléchargement terminé. Vous devrez peut-être également implémenter des méthodes NSURLSessionDelegate ou NSURLSessionTaskDelegate si vous devez gérer l’authentification ou d’autres événements dans le cycle de vie de la session. Vous devriez consulter le Cycle de vie du document d’Apple d’une session d’URL avec des délégués personnalisés, qui explique le cycle de vie complet de tous les types de tâches de session.

Aucune des NSURLSessionDownloadDelegate méthodes déléguées n’est facultative, bien que la seule où nous devons agir dans cet exemple soit . Lorsque le téléchargement de la tâche est terminé, une URL temporaire vers le fichier sur le disque vous est fournie. Vous devez déplacer ou copier le fichier dans le stockage de votre application, car il sera supprimé du stockage temporaire lorsque vous revenez de cette méthode déléguée.

#Pragma Mark - NSURLSessionDownloadDelegate- (void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{ NSLog(@"downloadTask:%@ didFinishDownloadingToURL:%@", downloadTask.taskDescription, location); // Copy file to your app's storage with NSFileManager // ... // Notify your UI}- (void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{}- (void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{}

Si votre application est toujours en cours d’exécution au premier plan lorsque la tâche de session d’arrière-plan est terminée, le code ci-dessus sera suffisant. Dans la plupart des cas, cependant, votre application ne sera pas en cours d’exécution ou elle sera suspendue en arrière-plan. Dans ces cas, vous devez implémenter deux méthodes de délégués d’application afin que le système puisse réveiller votre application. Contrairement aux rappels de délégués précédents, le délégué d’application est appelé deux fois, car vos délégués de session et de tâche peuvent recevoir plusieurs messages. La méthode de délégué d’application application: handleEventsForBackgroundURLSession: est appelée avant l’envoi de ces messages de délégué NSURLSession et URLSessionDidFinishEventsForBackgroundURLSession est appelée par la suite. Dans la première méthode, vous stockez un arrière-plan completionHandler, et dans la dernière, vous l’appelez pour mettre à jour votre interface utilisateur:

- (void) application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler{ // You must re-establish a reference to the background session, // or NSURLSessionDownloadDelegate and NSURLSessionDelegate methods will not be called // as no delegate is attached to the session. See backgroundURLSession above. NSURLSession *backgroundSession = ; NSLog(@"Rejoining session with identifier %@ %@", identifier, backgroundSession); // Store the completion handler to update your UI after processing session events ;}- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{ NSLog(@"Background URL session %@ finished events.\n", session); if (session.configuration.identifier) { // Call the handler we stored in -application:handleEventsForBackgroundURLSession: ; }}- (void)addCompletionHandler:(CompletionHandlerType)handler forSession:(NSString *)identifier{ if () { NSLog(@"Error: Got multiple handlers for a single session identifier. This should not happen.\n"); } ;}- (void)callCompletionHandlerForSession: (NSString *)identifier{ CompletionHandlerType handler = ; if (handler) { ; NSLog(@"Calling completion handler for session %@", identifier); handler(); }}

Ce processus en deux étapes est nécessaire pour mettre à jour l’interface utilisateur de votre application si vous n’êtes pas déjà au premier plan lorsque le transfert d’arrière-plan est terminé. De plus, si l’application ne s’exécute pas du tout à la fin du transfert en arrière-plan, iOS la lancera en arrière-plan et les méthodes d’application et de délégué de session précédentes sont appelées après application:didFinishLaunchingWithOptions:.

Configuration et limitation

Nous avons brièvement abordé la puissance des transferts en arrière-plan, mais vous devriez explorer la documentation et examiner les options NSURLSessionConfiguration qui prennent le mieux en charge votre cas d’utilisation. Par exemple, NSURLSessionTasks prend en charge les délais d’expiration des ressources via la propriété timeoutIntervalForResource de NSURLSessionConfiguration. Vous pouvez l’utiliser pour spécifier combien de temps vous souhaitez autoriser la fin d’un transfert avant de renoncer complètement. Vous pouvez l’utiliser si votre contenu n’est disponible que pour une durée limitée ou si l’échec du téléchargement ou du téléchargement de la ressource dans le délai imparti indique que l’utilisateur ne dispose pas d’une bande passante Wi-Fi suffisante.

En plus des tâches de téléchargement, NSURLSession prend entièrement en charge les tâches de téléchargement, vous pouvez donc télécharger une vidéo sur votre serveur en arrière-plan et assurer à votre utilisateur qu’il n’a plus besoin de laisser l’application en cours d’exécution, comme cela aurait pu être fait dans iOS 6. Une bonne touche serait de définir la propriété sessionSendsLaunchEvents de votre NSURLSessionConfiguration sur NO, si votre application n’a pas besoin d’être lancée en arrière-plan une fois le transfert terminé. Une utilisation efficace des ressources système satisfait iOS et l’utilisateur.

Enfin, il y a quelques limitations dans l’utilisation des sessions en arrière-plan. Comme un délégué est requis, vous ne pouvez pas utiliser les méthodes de rappel simples basées sur des blocs sur NSURLSession. Le lancement de votre application en arrière-plan est relativement coûteux, les redirections HTTP sont donc toujours prises. Le service de transfert en arrière-plan ne prend en charge que HTTP et HTTPS et vous ne pouvez pas utiliser de protocoles personnalisés. Le système optimise les transferts en fonction des ressources disponibles et vous ne pouvez pas forcer votre transfert à progresser en arrière-plan à tout moment.

Notez également que NSURLSessionDataTasks ne sont pas du tout pris en charge dans les sessions en arrière-plan, et vous ne devez utiliser ces tâches que pour de petites demandes de courte durée, pas pour des téléchargements ou des téléchargements.

Résumé

Les puissantes nouvelles API multitâches et réseau d’iOS 7 ouvrent toute une gamme de possibilités pour les applications nouvelles et existantes. Considérez les cas d’utilisation de votre application qui peuvent bénéficier de transferts réseau hors processus et de données fraîches, et tirez le meilleur parti de ces nouvelles API fantastiques. En général, implémentez les transferts d’arrière-plan comme si votre application s’exécutait au premier plan, en effectuant les mises à jour appropriées de l’interface utilisateur, et la majeure partie du travail est déjà effectuée pour vous.

  • Utilisez la nouvelle API appropriée pour le contenu de votre application.

  • Soyez efficace et appelez les gestionnaires d’achèvement le plus tôt possible.

  • Les gestionnaires d’achèvement mettent à jour l’instantané de l’interface utilisateur de votre application.

Autres Lectures

  • Session WWDC 2013 « Quoi de neuf avec le multitâche »

  • Session WWDC 2013  » Quoi de neuf dans le réseautage des Fondations »

  • Guide de Programmation du Système de Chargement d’URL

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.