Synchronisation automatique

muqaddarmuqaddar Administrateur
novembre 2014 modifié dans API UIKit #1

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

  • AliGatorAliGator Membre, Modérateur
    - NSURLSessionDownloadTask est pour lancer un téléchargement de données en tâche de fond. Ca te permet de laisser iOS lancer un téléchargement (GET) de gros fichiers même quand ton appli n'est plus lancée

    - 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)
  • muqaddarmuqaddar Administrateur
    mai 2014 modifié #3


    - NSURLSessionDownloadTask est pour lancer un téléchargement de données en tâche de fond. Ca te permet de laisser iOS lancer un téléchargement (GET) de gros fichiers même quand ton appli n'est plus lancée




     


    Y compris si toutes les tâches NSURLSessionDownloadTask sont dans une NSOperationQueue ?


     


     




    - 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.




     


    Oui, ce que je disais dans mon message précédent. On trouve beaucoup de tutos là -dessus...


     




    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.




     


    Tout à  fait.


    Mon code actuel fait:


    1. login 
    2. pull server items (id + date)
    3. compare to local items
    4. push items IDs to pull from server
    5. pull server items
    6. pull download items + pull download images (OperationQueue)
    7. push items to server (OperationQueue)

     




    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)




     


    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.


  • muqaddarmuqaddar Administrateur

    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.

  • AliGatorAliGator Membre, Modérateur
    Ah tu veux dire que tu ne t'étais jamais mis à  GCD ?
    (Ni même à  NSOperationQueue et sa méthode "-addOperationWithBlock:") ?

    Ah ouais ça a dû te faire un bond alors ;)
  • muqaddarmuqaddar Administrateur


    Ah tu veux dire que tu ne t'étais jamais mis à  GCD ?

    (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é...

  • AliGatorAliGator Membre, Modérateur
    Perso je ne pourrais plus m'en passer ^^
  • muqaddarmuqaddar Administrateur

    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.


  • AliGatorAliGator Membre, Modérateur
    Si je voulais une file d'attente avec une seule opération à  la fois je pense que j'utiliserais NSOperationQueue et maxConcurrentOperations = 1, mais bon rien n'empêche d'utiliser dispatch_queue_create(SERIAL), ça revient au même

    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 !)
  • muqaddarmuqaddar Administrateur

    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).

  • AliGatorAliGator Membre, Modérateur
    J'utiliserais AFURLSessionManager et j'enverrai mes requêtes en batch. C'est à  lui ensuite de gérer que le URL Loading System soujacent de iOS n'enverra jamais 100 requêtes en parallèle, mais un maximum de je-ne-sais-pas-combien ; c'est déjà  prévu normalement, ça ne va pas créer 100 threads de téléchargement en parallèle mais bien sérialiser les requêtes comme il faut.
  • muqaddarmuqaddar Administrateur
    mai 2014 modifié #12


    J'utiliserais AFURLSessionManager et j'enverrai mes requêtes en batch. C'est à  lui ensuite de gérer que le URL Loading System soujacent de iOS n'enverra jamais 100 requêtes en parallèle, mais un maximum de je-ne-sais-pas-combien ; c'est déjà  prévu normalement, ça ne va pas créer 100 threads de téléchargement en parallèle mais bien sérialiser les requêtes comme il faut.




     


    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... ;)


  • AliGatorAliGator Membre, Modérateur
    Bah utilise NSURLSession alors. Je t'ai suggéré AFURLSessionManager plutôt que NSURLSession directement... parce que je croyais que tu utilisais AFNetworking, mais si c'est pas le cas et que tout le reste de ta sync repose sur NSURLSession bah utilise NSURLSession. Ca ne changera pas grand chose, Apple est assez intelligente pour sous le capot ne pas envoyer 100 requêtes en parallèle mais les sérialiser (fais l'essai tu verras, lance 100 requêtes d'un coup puis un un breakpoint, y'aura pas 100 threads de créés)
  • muqaddarmuqaddar Administrateur
    mai 2014 modifié #14

    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


  • muqaddarmuqaddar Administrateur
    novembre 2014 modifié #15

    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.


  • AliGatorAliGator Membre, Modérateur
    Et pourquoi pas utiliser le mécanisme de BackgroundFetch ? Qui détermine tout seul quand l'utilisateur utilise en général ton appli et déclenche la méthode au moment opportun?
  • muqaddarmuqaddar Administrateur

    Parce que je n'ai pas du NSURLSession uniquement ?


    (cf plus faut dans le thread)


Connectez-vous ou Inscrivez-vous pour répondre.