Synchronisation automatique
Salut,
Selon la doc Apple, un background fetch ne peut durer plus de 30 secondes.
- (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...);
}
Cela comprend le traitement et l'affichage des nouvelles données reçues.
C'est très bien pour la batterie et pour télécharger de petites quantités de données et les traiter.
Mais je dois dire que je suis un peu "perdu" pour faire une synchronisation totale. Qui ne vas pas uniquement télécharger des données mais en uploader, et possiblement des tonnes d'images... donc les 30 secondes ne suffiraient pas pour un tel cas. Il y aurait de nombreux aller-retours réseau en plus.
C'est là où je me dis que ce n'est pas pour moi ?
J'ai vu de nombreux tutoriaux qui utilisent background fetch sur le net, pour télécharger quelques données du net. Ils sont souvent mariés à NSURLSessionDataTask pour le téléchargement.
Mon incompréhension est un peu liée au background fetch (qui a l'air de se marier avec tout, y compris avec NSURLConnection), et le NSURLSessionConfiguration en background qui peut lancer des téléchargements/uploads en background, recommandé pour des images...
Donc, j'ai du mal à voir si je dois marier les 2 ou oublier le background fetch qui n'est pas pour une synchronisation "lourde" ? Mon seul but est de passer ma synchronisation en automatique. Cette synchronisation fait des échanges clients/serveurs multiples (avec et sans REST), doit gérer les queues de téléchargement et l'upload/download d'images de 200 Ko environ.
Mon but: que l'utilisateur n'ait plus à appuyer sur synchroniser tous les 2 jours, et que les dernières données envoyées depuis un autre device soient présentes quand il ouvre son application.
L'idéal serait donc aussi de synchroniser quand l'appli est en arrière-plan également et pas seulement que l'application termine les synchro lancées quand elle était au premier plan.
Est-ce qu'on peut imaginer un background fetch (qui sait quand il doit s'exécuter il paraà®t) qui lancerait des synchronisations avec backgroundSessionConfiguration ce quiu permettrait de "passer outre" le délai du completionHandler à appeler sous 30 secondes ?
Réponses
- BackgroundFetch est fait pour mettre à jour rapidement tes données, c'est à dire surtout pour récupérer des données à jour pour que ton appli affiche les derniers éléments dès que tu la lances sans que l'utilisateur ait à attendre un refresh. Cas d'utilisation typique : application de news qui va récupérer en tâche de fond les derniers articles tous les matins à 8h55 car il a détecté que tu ouvrais l'appli régulièrement tous les jours vers 9h.
Dans les 2 cas, je pense que ce n'est pas adapté à ton cas. Ton cas fait + que juste télécharger un gros fichier (ce que ferait NSURLSessionDownloadTask) puisqu'il pousse aussi des données et fait aussi tout un algo de synchro. Il fait + qu'une bête mise à jour au sens "téléchargement des nouveaux articles / de nouvelles listes de cépages", etc.
Ton algo de synchro est trop compliqué d'après ce que j'en comprends et ce que tu expliques, pour tenir en tâche de fond. Ou alors il faudrait que tu revois ton algo de synchro (passer par du JSON-Patch par exemple ou autre)
Y compris si toutes les tâches NSURLSessionDownloadTask sont dans une NSOperationQueue ?
Oui, ce que je disais dans mon message précédent. On trouve beaucoup de tutos là -dessus...
Tout à fait.
Mon code actuel fait:
Un algo de synchro, c'est toujours compliqué. Dans tous les cas, il faut comparer objets clients et serveurs et faire les downloads/upload en conséquence, sur le client et sur le serveur.
Après, rien ne m'empêcherait de télécharger un seul fichier JSON et pusher 1 seul fichier JSON... ou faire la même chose avec un zip des images...
JSON-patch, en y réfléchissant, c'est un peu ce que je fais (de loin) à l'étape 2 et 3... sans utiliser ce standard.
Finalement, j'ai décidé de passer ma synchro en async dans applicationDidBecomeActive(). ET du coup, j'apprends enfin GCD...
(sympa la conf de CocoaHeads de Rennes là -dessus).
Je trouve cela suffisant pour une synchro automatique et pour mon cas.
Rappatrier le JSON et l'analyser en async est super rapide, et en plus l'utilisateur a la main sur toute l'UI pendant ce temps... La priorité c'est de "tirer" ce qui vient du serveur (les data d'abord, puis les images), puis de faire le push.
ça a l'air de bien tourner, et j'ai optimisé la génération du json côté serveur pour gagner encore du délai.
On pourra donc synchroniser de 2 façons:
- synchro au réveil de l'app (avec petit indicateur dans un coin) - désactivable dans les préférences
- synchro manuelle comme avant (certains utilisateurs ne veulent pas de l'automatique, si, si...)
Je vais continuer tout cela. Je ne sais pas encore si je vais utiliser le téléchargement en backgroundSession pour les images.
(Ni même à NSOperationQueue et sa méthode "-addOperationWithBlock:") ?
Ah ouais ça a dû te faire un bond alors
Si j'avais utilisé NSOperationsQueue indirectement via AFNetworking 1.x.
Mais pas, par exemple, les dispatch_async() persos... il faut en avoir l'utilité...
Qu'utiliserais-tu en corrélation avec NSURLSessionDownloadTask pour créer une queue de requêtes pour télécharger des images (1 requête par image, 1 opération concurrente à la fois) ? NSOperationQueue et addOperationWithBlock() OU dispatch_queue_create() ?
AFNetworking a une méthode pour gérer les batches:
https://github.com/AFNetworking/AFNetworking#batch-of-operations
mais elle en marche qu'avec NSURLConnection et non NSURLSession, donc je vais m'en passer.
Mais pour NSURLSessionDownloadTask ce n'est pas si simple, car ce n'est pas une opération synchrone.
Donc au lieu de ça je ferais ma propre NSMutableArray* requestQueue, et à chaque completion de NSURLSessionDownloadTask je regarderais s'il y a du monde dans la requestQueue, et si oui je dépile la première requête (FIFO) et l'exécute. Comme ça je suis sûr d'attendre que chaque requête asynchrone ait fini avant de lancer la suivante.
Ceci dit, je ne suis pas sûr de comprendre l'intérêt d'avoir qu'une seule opération / requête à la fois.
Synchroniser plusieurs requêtes, pour n'exécuter ensuite du code qu'une fois que toutes les réponses à ces requêtes sont arrivées, oui pourquoi pas (et ça j'ai un outil pour ça dans mes frameworks, c'est pas bien sorcier). Mais les faire une par une j'ai jamais eu vraiment le besoin (et ça doit pas mal ralentir tout l'algo si tout est obligatoirement dépendant et en série !)
Ma réflexion est: si tu télécharges plusieurs images à la fois, ta bande passante est partagée, donc on ne doit pas gagner grand chose.
Et je préfère avoir 2 images téléchargés sur 5 plutôt que 5 sur 5 qui sont partielles suite à un incident (genre coupure réseau). Alors d'accord, on peut reprendre le téléchargement où il en est avec NSURLSession mais en attendant les 5 images n'apparaà®tront pas car incomplètes.
Cela dit, dis-moi comment tu ferais pour le mieux, pour télécharger en moyenne 100 images de 200 Ko en background, sachant que je dispose de l'array des images à télécharger (recordID...) et à énumérer. Je veux juste envoyer ça dans une queue et éventuellement gérer les téléchargements incomplets (et rafraà®chir le main thread et la vue à la fin de la queue).
OK.
J'y avais pensé mais ça serait la seule portion de la sync qui ne reposera pas sur NSURLSession du coup... un peu dommage, non ? Même si je sais bien qu'ils ne vont pas déprécier NSURLConnection avant iOS 10...
J'utilise AFNetworking en effet partout dans l'application, mais ce dernier est compatible avec NRURLSession hein... sauf pour les queues.
EDIT: je ne suis pas le seul que ça tracasse:
http://stackoverflow.com/questions/19414486/how-to-batch-request-with-afnetworking-2
https://github.com/AFNetworking/AFNetworking/issues/1504
Salut,
J'ai repris ma synchronisation automatique hier, après 4 mois à faire autre chose.
Actuellement, je lance la synchro au réveil de l'application applicationDidBecomeActive() en arrière-plan.
Cela marche bien (en local du moins).
Maintenant, quelles sont les bonne façons de faire pour que ce soit envoyé sur le serveur de temps en temps ?
Déjà , j'hésite à le mettre aussi dans applicationWillResignActive() également.
Mes tests me laissent à penser que ça a l'air de marcher. Mais de mémoire, il y a 10 minutes de délai de travail en background max (je ne sais pas si ça a changé), ce qui devrait suffire pour 99% des utilisateurs ?
(là j'ai synchronisé en 2 secondes avec mes tests légers...)
Ensuite, quand l'utilisateur se sert de l'appli, et qu'elle est donc au premier plan, j'envisage une préférence:
- synchroniser toutes les 5 minutes
- synchroniser toutes les 15 minutes
- synchroniser toutes les heures (mais cela sous-entend que le gars se sert de mon appli 1 heure durant... ce qui doit être très très rare.)
Ou bien essayer de deviner quand il est intéressant de synchroniser ? Quand un modèle est touché en CRUD ? Mais c'est à dire tout le temps en gros...
Les bonnes pratiques sont les bienvenues.
Parce que je n'ai pas du NSURLSession uniquement ?
(cf plus faut dans le thread)