dispatch_async subtilities

amadehamadeh Membre
décembre 2014 modifié dans API UIKit #1

Bonjour à  tous,


 


Je me pose une question de curiosité :


 


Imaginons que j'ai une VC dans laquelle je lance un appel à  un webservice avec une dispatch_async qui prend... disons 5 secondes pour répondre et que en réponse à  ce webservice, l'interface utilisateur est modifié. Imaginons ensuite que l'utilisateur n'a pas la patience d'attendre la réponse du webservice et qu'il aille sur une autre VC avant la réponse du webservice.


 


Que se passe-t-il quand le webservice répond et qu'il va modifier les éléments d'UI de la première VC ?


 


Que ce passe-t-il dans les détails ?


Réponses

  • jpimbertjpimbert Membre
    décembre 2014 modifié #2

    Il se passe ce que le développeur a décidé qu'il se passe (plus ou moins volontairement)


     


    Si tu ne fais rien de spécial :


    - soit la vue qui n'est plus affichée est toujours en mémoire, dans ce cas la vue est mise à  jour mais l'utilisateur ne voit rien. Le problème c'est surtout que lorsque l'utilisateur va réafficher la vue, si tu ne fais rien de spécial tu vas relancer la requête au webservice et l'utilisateur risque encore de s'impatienter


    - soit elle n'est plus en mémoire, et suivant les cas il ne se passe rien (un message envoyé à  un objet nil ne fait rien) ou ça crashe


     


    Dans ce genre de cas, et afin de garder un bon UX, j'ai la pratique habituelle suivante :


    - mon VC vérifie si le modèle (selon MVC) doit être mis à  jour avant de lancer la requête WS


    - la requête WS est généralement prise en charge par une classe du modèle (sans lien direct avec l'UI, la partie V du MVC)


    - lorsque la requête se termine 1/ le modèle est mis à  jour et 2/ le VC s'il est toujours vivant est informé que le modèle a été mis à  jour (en OSX on ferait simplement un bind)


  • Hello,


     


    Si je ne me trompe pas c'est pour ça qu'Apple a inventé KVO :-)


     


     


    Le VC s'abonne à  un objet et lorsque cet objet change d'état le VC doit être notifié via KVO. Et bam le changement d'état est pris en compte.


     


    Tout simplement.


     


    Note: Pour ça l'appel au webservice de doit pas se faire une classe VC mais une autre classe dont le VC s'est abonné à  l'objet en question.


     


    K.


  • AliGatorAliGator Membre, Modérateur
    Je plussoie la réponse de jpimbert.

    Le code fait ce que le développeur lui dit de faire.

    Les blocks capturent les variables qu'ils utilisent (je t'invite à  relire la doc "Blocks Programming Topics" de la doc Apple à  ce sujet). Ce qui permet de s'assurer que ces objets sont toujours en vie quand le block s'exécute. Si ton block référence un UITextField qui était dans ton interface, il va être retenu par le block jusqu'à  ce que ce dernier s'exécute et relâché ensuite. Même si ton textField n'est plus à  l'écran et n'est pas visible actuellement.

    Du coup à  toi de réfléchir à  la stratégie que tu veux. Le mettre à  jour alors qu'il n'est plus utilisé n'as pas forcément de sens (et dans ce cas on utilise le pattern weakSelf/strongSelf). D'un autre côté ça peut être justifié si l'utilisateur risque de revenir sur l'écran plus tard (la même instance du VC s'entend). Sauf que ça peut être aussi une meilleure stratégie de faire l'update dans le viewWillAppear ou viewDidAppear seulement quand l'écran redevient visible, plutôt que le faire quand il ne l'est pas... ca dépend vraiment de ce que tu veux, de ton cas d'usage, à  toi de réfléchir à  la question.
  • Hello,


     


    Question et réponses intéressantes mais j'ai pas trop compris vos explications :)


     


    voici un exemple de code que j'utilise. 



    @property (nonatomic, strong) DataManager *dataManager;

    - (void)viewWillAppear:(Bool)animated {
       [self fetchAccounts];  
    }

    - (void)fetchAccounts {
     
    .... weakSelf =... 
     [self.dataManager fetchAccounts:(void (^) (NSArray *accounts,...)) {
    .... strongSelf =...
       [strongSelf.tableView reloadData];
     }];
    }

    Que ce qui ce passe dans le cas ou le UIViewController n'est plus en mémoire ?  ( DataController est un singleton).


     


    Disons que le block retient les objet qu'il référence, dans ce cas la tableView est toujours en mémoire même si le controller est dealloc et donc elle va faire un reloadData ... et peut être bonjour les dégâts...


     


    Est-ce que mon raisonnement est bon ?


     


    Comment vous gérer ce pattern ( un contrôleur qui demande des données au dataManager et qui met à  jour sa vue). ?


     


    Merci 

  • AliGatorAliGator Membre, Modérateur
    Non.

    Puisque tu as fait dans ton exemple le mécanisme weakSelf/strongSelf, ça veut dire que le block ne va pas retenir la variable weakSelf, donc quand il va recréer une référence forte dessus ("strongSelf = weakSelf" à  l'intérieur du block), weakSelf aura eu le temps de repasser à  nil. Comme weakSelf, par définition puisqu'il est weak, ne va pas être retenu par le block, il peut tout à  fait avoir disparu entre temps.

    Du coup quand ton block va s'exécuter, si ton UIViewController (weakSelf) a été détruit entre temps car personne ne le retenais plus, tu vas avoir l'équivalent de "strongSelf = nil" et forcément demander la tableView sur nil va retourner nil, le message "reloadData" va être envoyé sur "nil", tu n'aurais rien de spécial d'exécuté.


    Si tu n'avais pas mis en place le weakSelf/strongSelf, mais utilisé self directement, ton block aurait retenu self (ton UIViewController) et l'aurait gardé en vie (même s'il n'est plus affiché à  l'écran) jusqu'à  ce que le block se soit exécuté, et aurait encore à  ce moment là  ton self et donc ton self.tableView et aurait donc bien appelé reloadData sur ta tableView. Ce qui peut être une bonne chose. Ou pas. Selon ce que tu veux faire (pourquoi tu dis "bonjour les dégâts ?")
  • Bonjour messieurs,


     


    Je suis content de l'effervescence, merci. Je vous rassure, ce n'est pas un besoin, c'est juste une question que je me posais. Vous m'avez appris des choses intéressantes que je vais fouiller.


  • Merci c'est plus clair.


     




     (pourquoi tu dis "bonjour les dégâts ?")




     


    Parce que j'ai mélangé les choses. Dans ma tête le block vas retenir carrément la TableView :).


  • Joanna CarterJoanna Carter Membre, Modérateur



    - (void)fetchAccounts {
     
    .... weakSelf =... 
     [self.dataManager fetchAccounts:(void (^) (NSArray *accounts,...)) {
    .... strongSelf =...
       [strongSelf.tableView reloadData];
     }];
    }



     


    N'oublies pas



    - (void)fetchAccounts {

    .... weakSelf =...
    [self.dataManager fetchAccounts:(void (^) (NSArray *accounts,...))
    {
    if (weakSelf)
    {
    .... strongSelf = weakSelf
    [strongSelf.tableView reloadData];
    }
    }];
    }

  • zoczoc Membre
    décembre 2014 modifié #10

     [self.dataManager fetchAccounts:(void (^) (NSArray *accounts,...))
    {
    if (weakSelf)
    {
    .... strongSelf = weakSelf
    [strongSelf.tableView reloadData];
    }
    }];
    }

    Ce code n'est pas sûr. En effet weakSelf peut passer à  nil entre le if et l'affectation de strongSelf. La bonne façon de faire est donc :
     
     [self.dataManager fetchAccounts:(void (^) (NSArray *accounts,...))
    {
    .... strongSelf = weakSelf
    if (strongSelf)
    {
    [strongSelf.tableView reloadData];
    }
    }];
    }
    Bon après dans le cas d'envoi de messages ce n'est pas grave puisqu'un message envoyé à  nil ne provoquera pas de plantage ni de comportement non attendu...
  • J'avoue que j'utilise ce pattern weakSelf/strongSelef d'une manière machinale. Je veux dire par la que je n'est jamais vue un bug ou le weakSelf passe à  nil dans le block et j'ai déjas vérifier les recommendations d'Apple et ils parlent pas de strongSelf mais juste de weakSelf.


     


    Est-ce que on peut reproduire ce cas de weakSelef qui passe a nil avant la fin de l'exécution du block ? ( un bout de code ça serai parfait).


     


    Est-ce que c'est relatif au temps d'exécution ? 

  • AliGatorAliGator Membre, Modérateur
    C'est ce qu'on appelle une racing condition en multi-threading " très difficile à  reproduire du coup par définition.


    Par contre quand tu dis "j'utilise ce pattern machinalement" je t'invite à  y réfléchir d'avantage. Ce pattern n'est à  utiliser que quand c'est nécessaire, pas à  toutes les sauces sans réfléchir. Des fois au contraire il fait s'assurer que le block retient l'objet qui t'intéresse. Ca depend de ton cas d'usage et il ne faut pas l'utiliser sans réfléchir mais reflechir à  chaque usage selon ton cas d'usage et ton besoin.
  • C'est effectivement le genre de bugs le plus difficile à  reproduire, puisqu'en général ça se joue à  1 ou 2 instructions machine près. Il faut que l'ordonnanceur donne la main à  un thread qui libérera l'objet entre l'exécution de l'opcode qui effectue la comparaison (condition du if) et l'affectation.
  • Pour d'autres qui auraient eu aussi ce questionnement, voilà  quelques liens sympas pour fouiller davantage la question :


     


    https://dhoerl.wordpress.com/2013/04/23/i-finally-figured-out-weakself-and-strongself/


     


    http://masteringios.com/blog/2014/03/06/avoid-strong-reference-cycles/


     


    Et le Blocks Programming Topics.

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