[Résolu] Chargement d'un XML volumineux : En background - Multi view

2

Réponses

  • @denis : KVO, ce n'est pas une notification ?


  • Je me lance dans les observer alors : en java j'ai les reflexes observer mais là  comme je ne connais pas comment l'utiliser ... KVO pour le moment j'essaie de comprendre sur ce site




  • @denis : KVO, ce n'est pas une notification ?




     


    dans KVO tu observes la modification d'une variable d'instance, le choix alternatif c'est d'utiliser un centre de notification (NSNotificationCenter), qui dispatch des messages à  ses "observers". Rien de bien compliqué mais dans le cas des notification, il faut bien se retirer de la liste des observateurs avant que l'objet soit désaloué, dans le cas du KVO, le plus critique est souvent d'arrêter d'observer un objet avant qu'il soit désaloué, mais s'il s'agit d'un singleton c'est plus simple...

  • AliGatorAliGator Membre, Modérateur

    KVO (ou l'utilisation Notifications) ne sont pas des patterns les plus adaptés pour ce genre de cas.


     


    Le pattern clé pour ça c'est plutôt un delegate ou plutôt un completionBlock qui est appelé quand le parsing est terminé.


    Donc un objet qui a une méthode (de classe ?) qui se charge de faire la requête, récupérer le JSON en réponse, le parser, créer les objets modèles à  partir de ce JSON (le tout potentiellement dans une queue dédiée), puis appeler la completionBlock pour retourner lesdits objets.


     


    Et tu appelles cette méthode dans ton ViewController en lui fournissant un completionBlock qui va faire ce que tu veux des données une fois arrivées.




  • KVO (ou l'utilisation Notifications) ne sont pas des patterns les plus adaptés pour ce genre de cas.


     


    Le pattern clé pour ça c'est plutôt un delegate ou plutôt un completionBlock qui est appelé quand le parsing est terminé.


    Donc un objet qui a une méthode (de classe ?) qui se charge de faire la requête, récupérer le JSON en réponse, le parser, créer les objets modèles à  partir de ce JSON (le tout potentiellement dans une queue dédiée), puis appeler la completionBlock pour retourner lesdits objets.


     


    Et tu appelles cette méthode dans ton ViewController en lui fournissant un completionBlock qui va faire ce que tu veux des données une fois arrivées.




     


    Bonsoir Ali,


     


    il me semble que dans son explication, il a un gros fichier XML à  importer et que son contrôleur peut potentiellement être désaloué au moment ou les données sont disponibles (dans le cas ou l'utilisateur annule la manipe) et qu'il ne veut pas perdre les données dont le chargement a démarré au moment où la vue est affichée, que se passe-t-il avec le bloque dans ce cas là  ?

  • AliGatorAliGator Membre, Modérateur
    Bah c'est pour ça que je propose le block comme solution justement.


    Après ce qui ce passe ça dépend comment il l'utilise.

    - S'il référence directement self dans le block alors le block va capturer self (le ViewController dans l'exemple dont on parle) donc même si l'écran a disparu le VC sera retenu jusqu'à  ce que le completionBlock ait été exécuté et relâché après.

    - S'il utilise une weak reference vers self dans son block, le block ne va pas retenir le VC et quand le VCva disparaà®tre et va être desalloué, la weak reference va passer à  nil toute seule (c'est un peu l'intérêt des weak références) donc quand son completionBlock va s'exécuter il va envoyer des messages à  nil qui n'auront aucun effet.


    Donc c'est aussi l'avantage de ce pattern.


    Pour les rappels sur tout ça relire la doc concernant le Memory Management et les Memory Policies, comme la doc sur weak vs strong, ainsi que le Blocks Programming Guide qui explique tous ces cas et fonctionnements.


  • Bah c'est pour ça que je propose le block comme solution justement.


    Après ce qui ce passe ça dépend comment il l'utilise.

    - S'il référence directement self dans le block alors le block va capturer self (le ViewController dans l'exemple dont on parle) donc même si l'écran a disparu le VC sera retenu jusqu'à  ce que le completionBlock ait été exécuté et relâché après.

    - S'il utilise une weak reference vers self dans son block, le block ne va pas retenir le VC et quand le VCva disparaà®tre et va être desalloué, la weak reference va passer à  nil toute seule (c'est un peu l'intérêt des weak références) donc quand son completionBlock va s'exécuter il va envoyer des messages à  nil qui n'auront aucun effet.


    Donc c'est aussi l'avantage de ce pattern.


    Pour les rappels sur tout ça relire la doc concernant le Memory Management et les Memory Policies, comme la doc sur weak vs strong, ainsi que le Blocks Programming Guide qui explique tous ces cas et fonctionnements.




     


    en effet si le block continue sa vie (j'avais un doute, mais j'aurais du le vérifier) c'est bien mieux, avec la weak reference ce sera parfait et le tout bien moins casse gueule que de gérer une une observation.

  • @denis, tu voulais dire sans la weak reference.


     


    La weak reference est utile lorsque qu'une classe garde un pointeur strong vers un block qui lui-même utilise self.qqCh.


  • denis_13denis_13 Membre
    septembre 2014 modifié #40


    @denis, tu voulais dire sans la weak reference.


     


    La weak reference est utile lorsque qu'une classe garde un pointeur strong vers un block qui lui-même utilise self.qqCh.




     


    je veux dire avec la weak reference, cela permet justement de mettre la vue à  jour quand elle n'est pas désalouée sans la retenir (exactement comme l'a suggéré Ali).


  • Am_MeAm_Me Membre
    septembre 2014 modifié #41


    KVO (ou l'utilisation Notifications) ne sont pas des patterns les plus adaptés pour ce genre de cas.


     


    Le pattern clé pour ça c'est plutôt un delegate ou plutôt un completionBlock qui est appelé quand le parsing est terminé.


    Donc un objet qui a une méthode (de classe ?) qui se charge de faire la requête, récupérer le JSON en réponse, le parser, créer les objets modèles à  partir de ce JSON (le tout potentiellement dans une queue dédiée), puis appeler la completionBlock pour retourner lesdits objets.


     


    Et tu appelles cette méthode dans ton ViewController en lui fournissant un completionBlock qui va faire ce que tu veux des données une fois arrivées.




     


    Donc je récapitule (je vais pas vous mentir je suis un peu perdu (dans la conversation je n'ai pas eu bcp de temps hier soir), KVO, etc ..)


     


    1) Donc si je comprend bien le KVO n'est pas utile dans mon cas


    2) J'ai 2 solutions qui s'offre à  moi (d'après Ali) : ces 2 solutions doivent résonner avec un bloc.


        a ) Soit avec une référence self : référence de ma SearchViewController.


        b ) Soit je passe par un weak référence ==> qu'est ce qui doit être en __weak ? je ne comprend pas trop


     


    Aussi ces 2 solutions sont couplées avec l'histoire du singleton. Et le singleton (Model) me permettra de respecter le principe MVC


     


    MAJ : j'ai trouvé ce tuto Raywenrlich iOS Design Pattern je vais m'en inspirer je pense


  • denis_13denis_13 Membre
    septembre 2014 modifié #42

    une référence "weak", c'est quelque chose que tu dois étudier et que tu n'as pas rencontré sous java... En fait cela est lié au à  gestion de la mémoire en Objective-C. Il n'y a pas de garbage collecteur, mais les objets on un compteur (le retain count) pour indiquer au système quand l'objet est à  éliminer de la mémoire. Depuis quelques temps on ne le vois plus beaucoup car Apple a proposé un système de gestion automatique (ARC automatic retain count), ce qui a modifié la terminologie et certaines pratiques. Pour résumer pour qu'un objet soit éliminé en mémoire il faut que son compteur soit à  zéro. Quand tu as une variable d'instance qui est un objet (une "property" de type objet), c'est à  toi d'augmenter son retain count pour dire au système que tu ne veux pas qu'il disparaisse, ce qui est fait automatiquement par l'usage du mot clef "strong" au moment de la déclaration.


    Dans le cas qui t'intéresse, tu lances la recherche de tes données dans un bloc, et une action sera lancée au moment où tes données seront disponibles (c'est à  dire après le chargement du contenu de ton url). Dans ce bloc dont l'execution est lancé, donc de manière asynchrone, tu vas mettre tes données en cache, mais aussi mettre ta vue à  jour, le cache est toujours dispo, pas de problème, mais peut-être que ta vue (la vue "détails"), ne serait plus utilisée car l'utilisateur y aura renoncée et revenue en arrière avant l'arrivée des données. Donc la vue n'existe plus ... tout va très bien elle est "nulle et ne la met pas à  jour.


    Le problème vient du fait que si dans ce bloc tu as introduit une référence "strong" à  ton contrôleur, ce dernier ne pourra pas être élimé automatiquement car en établissement une référence strong dans ton bloc, le retain count du contrôleur aura été augmenté. Visiblement il ne se passera rien d'anormal, mais  à  chaque mainipe, tu vas augmenter l'emprunte mémoire de ton application, fuite mémoire... le mal !!


    Donc l'idée est d'avoir une weak référence sur ta vue, elle n'augmentera pas son retain count, et donc la vue vivra sa vie comme elle veut et sera éliminée de la mémoire au bon moment.


    Si elle est encore là  quand le résultat arrive dans bloc, super, tu la mets à  jour, sinon l'objet pointé sera null et il n'y aura rien à  faire (à  part de mettre ton cache à  jour).


  • Am_MeAm_Me Membre
    septembre 2014 modifié #43

    Ok merci pour l'explication. (en gros weak est plus intéressant à  utiliser dans mon cas)


    Mais vu que la conversation est parti dans plusieurs directions ça m'a troublé. De plus quand tu dis que l'utilisateur peut renoncer à  afficher la vue détaillé certes c'est vrai MAIS moi je veux stocker le résultat une bonne fois pour toute. 


     


    J'ai plusieurs choses à  faire du coup.


    1) Appliquer le MVC sur mon projet


    2) Comprendre comment une fonction bloc : mais je n'en vois pas l'utilité dans mon cas, car j'utilise deja le parserDelegate dans un bloc de NSUrlSessionDataTask ... donc c'est déjà  asynchrone non ?


    3) Créer mon singleton (doit-il utilisé ARC ou non ????) je pars sur du ARC vu que j'ai jamais fait l'inverse


     


     


    Ensuite Aligator m'a conseillé un delegate mais moi personnellement je comprends le delegate (moyennement) mais au-delà  de créer un delegate ...


     


     


     


    PS : En résumé je suis vraiment perdu là . Mais bon je vais me débrouiller merci à  tous ;)


  • Am_MeAm_Me Membre
    septembre 2014 modifié #44

    Et si j'ai tout compris par exemple l'utilisateur fait 



    Dans mon SearchViewController:

    singleton = [MaClassSingleton sharedSingleton];
    [singleton traiteXML]

    Cela implique que dans ma classe Singleton je dois faire appel à  une méthode du modèle qui me retournera des données que je traiterai dans mon singleton ? Et cette fonction doit être dans un bloc c'est ca ?



    Dans SingletonClass

    méthode traiteXML
    {
    //je dois avoir un pointeur vers le modèle du coup ?
    resultat = [modele effectueRequete:"www.toto.com/monXML.xml"];

    //Ma classe singleton a dans ses attribut un truc du genre NSDictionnary données;
    donnees = [Data dataWithResultat:résultat] //cette fonction est fictive biensur

    }


    Dans DetailViewController :

    - viewDidLoad()
    {
    LesDonneesA_Afficher = [singleton getDonnees];
    //Apres je fais l'initialisation du texte dans les différents label etc ...
    }

    VOilà  ai-je tout compris ou je suis OUT ?


  • AliGatorAliGator Membre, Modérateur
    Il est où le block pour signaler au VC appelant que l'opération asynchrone (requête réseau + parsing) est fini et les données dispos ?
  • Am_MeAm_Me Membre
    septembre 2014 modifié #46

    Bah dans la méthode j'ai un bloc :



    [modele effectueRequete:"www.toto.com/monXML.xml"];


    -(void) effectueRequete:(NSURLRequest*)request
    {
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request
    completionHandler:
    ^(NSData *data, NSURLResponse *response, NSError *error) {

    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
    CustomXmlDelegate *parserDelegate = [[CustomXmlDelegate alloc] init];

    [parser setDelegate:parserDelegate];
    [parser parse];


    //la je sais que j'ai fini



    }];
    [task resume];
    }


    Ou j'ai du mal comprendre. Comme je le disais au dessus je ne vois pas l'intérêt de créer un bloc si j'ai NSURlSessionDataTask qui le fait deja


  • AliGatorAliGator Membre, Modérateur
    Nan mais ton ViewController comment il sait que ta tâche est terminée et qu'il peut donc aller lire les données ? Lui il est pas au courant ! S'il demande un getDonnees immédiatement alors que la requête n'a pas fini et les données ne sont pas parsées et remplies, il risque d'avoir un nil ou un tableau vide à  la place à  les avoir demandées trop tôt !
  • Am_MeAm_Me Membre
    septembre 2014 modifié #48

    AH ce que tu es en train de me dire c'est que dans mon ViewController je dois avoir une méthode block et dans cette méthode je fait getData et c'est qu'a partir de la que je saura quand faire par exemple un refresh de la vue ?


     


    Mais attend ce que je comprend pas (reprenons les noms SearchViewController, HomeViewController, DetailViewController si ça ne te dérange pas) : 


     


    Dans ma SearchViewController :


    Dans le block en question que j'appel, c'est là  ou je dois faire intervenir le weak ou le self  (l'histoire ou même si l'utilisateur passe à  la HomeViewController ou DetailViewController ça puisse s'exécuter en tâche de fond)? 

  • AliGatorAliGator Membre, Modérateur
    Oui
  • Ok parfait je commence à  comprendre  :o


     


    Pour l'histoire du block moi j'ai pas besoin de passer de paramètre ni récupérer de résultat du coup.


    J'ai un peu cherché sur le net mais du coup je peux utiliser le block simple anonyme :



    Par exemple dans viewWillAppear

    -(void)viewWillAppear
    {

    [self viewWillAppear]
    //....

    {

    mesData = [[Singleton sharedSingleton]getData];

    //et la je met à  jour mes machins

    }

    //....
    }

    et la même si je change de vue le block continuera à  s'exécuter à  ce que je comprends

  • AliGatorAliGator Membre, Modérateur
    Il est où ton bloc dans ton exemple là  ? Et il est appelé d'où, où as-tu mis dans ton code que fallait l'appeler une fois le parsing fini ?
  • Am_MeAm_Me Membre
    septembre 2014 modifié #52

    Je reprend mon pseudo-code du dessus et dans SearchVC



    Dans mon SearchViewController:

    Dans une fonction comme viewWillAppear

    //mon bloc
    {
    singleton = [MaClassSingleton sharedSingleton];
    [singleton traiteXML]
    //la je met à  jour ce qu'il faut
    }
    //fin du bloc

    Quand j'ai essayé le bloc dans la doc d'Apple cela m'a mis une erreur alors j'ai pas trop compris pourquoi 



    ^{
    NSLog(@This is a block);
    }

  • Am_MeAm_Me Membre
    septembre 2014 modifié #53

    Et je me suis basé du coup sur ces site pour essayer de voir les différents modèle site1 et site2


     


    Et dans site2 le gas dis qu'un block inline c'est 


     


    {


     


    }


     


     


    PS : Quelque soit les langages je n'ai jamais "crée" mon propre block donc je commence à  assimiler. C'est pour ça que je veux comprendre comment le bloc peut continuer à  tourner en BackGround même si je change de vue. Au vue de la propriété __weak et du self. c'est un automatisme ... ? ou faut passer par 


     


    dispatch_async(dispatch_get_main_queue(), ^{


    });


  • AliGatorAliGator Membre, Modérateur
    ça s'appelle une closure ou parfois un lambda ça existe dans plein de langages (JavaScript, Java, ObjC, Swift, Ruby, etc)


    Je re-pose la question, par quel magie penses tu que ton bloc que tu as écrit dans le viewWillAppear va automatiquement être appelé seulement à  la fin de ton [parser parse] ? C'est pas juste ton commentaire "// la je sais que j'ai fini" qui fait que le bloc est appelé magiquement hein ;-)
  • Am_MeAm_Me Membre
    septembre 2014 modifié #55

    Magie :D je croyais que les lignes avec des // faisait tout pour nous .


     


    En fait je pensais plutôt à  l'inverse mon bloc appellerai mon parser à  travers la méthode traiteXML et qu'il attendrait le parseur.


     


    le fait que dans ma méthode traiteXML je vais avoir la ligne de code : 



    [parser parse]
    //j'attend ici la fin du parser et je quitte la fonction

    Du coup je sais que cette tant que le parseurDelegate n'a pas fini de parser la méthode -(void)traiteXML ne sera pas quitté et donc 


    Dans mon bloc 



    {
    traiteXML();
    //Il y aura une attente du [parser parse] non?
    }

    Ou je n'ai pas compris ta question.


  • AliGatorAliGator Membre, Modérateur
    Bah oui y'aura une attente du parseur mais cet appel bloquant du parseur il est lui même exécuté dans le completionBlock de ta requête, qui elle est asynchrone ! Donc tout le code dans le completionBlock de ton NSURLSession est exécuté sur un autre thread, seulement quand la requête est finie. Mais pendant ce temps où ta requête est envoyée en tâche de fond et attend d'avoir fini pour appeler son completionBlock, bah par définition puisque la requête est en background ça bloque pas l'exécution du reste de l'appli. Donc ton ViewControlleur là , il sait comment, lui, que tout ce patacaisse exécuté en arrière plan en parallèle est terminé ? Parce que lui il va pas attendre, il va lancer la requête mais il va pas attendre il va continuer ensuite (c'est l'intérêt de faire des requêtes asynchrones d'ailleurs)


    Ton traiteXML si son code ne fais qu'appeler [parser parse] alors oui lui il va être bloquant et attendre. Mais ta méthode effectueRequete que tu as mise dans le post tout en haut de cette page, qui envoie la requête en tâche de fond et retourne tout de suite la main, n'exécutant le completionBlock qui va parser ton XML que plus tard quand la réponse sera arrivé, bah cette méthode du coup ne prévient pas son appelant extérieur (le ViewController) que le code asynchrone qu'elle a lancé à  enfin fini ! Du coup comment tu sais quand est-ce que tu dois rafraà®chir ton écran avec les nouvelles données ?
  • Am_MeAm_Me Membre
    septembre 2014 modifié #57

    Ah oui ... suis-je bête  :o


     


    Alors donc on en arrive comment je fais ça ...


     


    1) Dans mon singleton j'ai un pointeur de la VC et dans ma VC j'ai une fonction block : c'est ce que j'ai essayé de modéliser dans ma tête mais je me suis dis que c'est une très très mauvaise idée


     


    2) J'ai lu un truc sur les dispatch_semaphore ... si je dois manipuler des sémaphore haha je me pend de suite (façon de parler ...)


     


    3) Je ne sais pas ... mais vous oui :P ?


  • faut pas dramatiser, si ta méthode "effectueRequete:" tu la lances dans ton contrôleur, tu peux lui indiquer ton contrôleur en variable de bloc, avec le mot clef __block) par exemple


     


    __weak MyObject *weakSelf = self;


     


    avant la ligne où tu déclare ta session.


     


    et la retrouver donc dans ton block pour l'utiliser à  la suite de la ligne [parser parse];


     


    si tu as une étiquette UILabel *monEtiquette;


     


    tu pourras y accéder:


     


    weakSelf.monEtiquette.text = @valeur parsée;


     


    ton affichage sera mis à  jour, sauf si le contrôleur n'existe plus weakSelf sera nul et il ne se passera rien.

  • Am_MeAm_Me Membre
    septembre 2014 modifié #59

    Oui mais le MyObject en haut va contenir mon Model. Car ma session est dans une méthode appelé dans le Modele.


     


     


    En gros : 


    Dans ma méthode traiteXML du singleton je vais faire appel à  une méthode du modèle qui effectuera la requête du coup je pige plus trop la.


     


    J'ai mal lu donc oui je pourrais le passer. Mais ou est le singleton dans l'histoire. Car c'est à  partir de lui que je vais faire appelé à  traiteXML : à  moins que je passe le contrôleur dans traiteXML qui lui même le passera au modèle.


     


    Du coup je n'ai pas besoin de créer de bloc ????


  • AliGatorAliGator Membre, Modérateur
    Il faut passer à  ta méthode effectueRequete: un paramètre supplémentaire, de type block (même si ce block ne te passe rien en retour dans ton cas) paramètre que tu vas pouvoir appeler dans le code de effectueRequete quand le parsing sera fini, et paramètre auquel tu vas pouvoir passer un block lors de l'appel depuis le VC pour lui dire quoi faire quand il est appelé.

    -(void)effectueRequete:(NSURLRequest*)request completion:( void(^)(void) )completion
    {
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request
    completionHandler:
    ^(NSData *data, NSURLResponse *response, NSError *error) {

    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
    CustomXmlDelegate *parserDelegate = [[CustomXmlDelegate alloc] init];

    [parser setDelegate:parserDelegate];
    [parser parse];


    //la je sais que j'ai fini --> j'appelle le block passé en paramètre à  effectueRequete pour prévenir
    completion();
    }];
    [task resume];
    }

    Tout simplement.


    Et du coup pour l'appeler, depuis ton ViewController :
    [modele effectueRequete:taRequete completion:^{
    // ton code à  exécuter quand la requête + le parsing de la réponse est fini
    // sans doute un code qui rafraà®chit l'interface avec les nouvelles données typiquement
    }];
  • Am_MeAm_Me Membre
    septembre 2014 modifié #61

    Ouah super je viens de comprendre que le complétion n'est exécuté qu'après même si je l'avais compris avec NSURLSession plus ou moins. Mais la j'ai compris comment ça marche en interne !!


     


    Merci à  vous 2 (et au autres qui sont intervenus au départ) je me lance dans le code et je marquerait résolu quand j'aurai la machine de guerre "les blocks" en marche


     


     


    Du coup plus besoin de passer le VC en paramètre. Vu que dans juste avant de faire completion() je vais mettre à  jour mon Singleton. Et du coup dans mon VC je saurai qu'a partir du completionHandler mon singleton sera mis à  jour


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