Blocks & variables

Bonjour tout le monde.



Je commence le développement d'un petit outil de messagerie instantané. Un des buts de son développement est de toucher à  de nouveau concepts que je n'ai encore jamais eu l'occasion d'utiliser. Ainsi, je viens de faire la connaissance de "Grand Central Dispatch" ainsi que des blocks.



Malgré la lecture de la documentation à  propos des blocks, j'ai un peu de mal à  comprendre leurs comportements avec les variables. Si j'ai bien compris la documentation, lors de la création d'un block, toutes les variables utilisées par celui-ci sont copiées en mémoire. Ainsi la modification d'une variable à  partir du block n'impacte pas le reste du scope et vice-et-versa. Ce comportement peut cependant être changé grâce au symbole "__block".



Cependant, même avec le mot clef "__block", je n'arrive pas à  manipuler les attributs de mon objet "self" à  partir du block. Est-il possible de le faire ? Y a-t-il une copie complète de mon objet en mémoire ? Dois utiliser des outils du type mutex ?



Merci d'avance de vos réponses !
Mots clés:

Réponses

  • Un block peut modifier les variables d'instances sans aucun problème. Il n'y a pas besoin d'utiliser __block.

    Si un block fait référence à  self, alors un retain est fait sur self, et non une copie. C'est d'ailleurs le cas pour tout objet utilisé dans le block. Une copie du pointeur est effectuée mais un retain est également fait sur l'objet (et un release une fois le block supprimé).



    __block n'est utile que pour modifier les variables (primitives ou pointeurs) de méthodes. Pour les variables de classes, ce n'est pas nécessaire.



    Si ça ne fonctionne pas, le problème est autre. Quelle est donc l'erreur exactement ?
  • AliGatorAliGator Membre, Modérateur
    septembre 2012 modifié #3
    'Yombes' a écrit:
    Cependant, même avec le mot clef "__block", je n'arrive pas à  manipuler les attributs de mon objet "self" à  partir du block. Est-il possible de le faire ?
    C'est tout a fait possible et normalement tout est même géré automatiquement. Si dans ton block tu utilisés une expression comme "self.mapropriete" alors le block va retenir "self" (pour être sur de pouvoir y accéder quand le block sera exécuté) et tu pourras donc récupérer la valeur de ta propriété et même la modifier (c'est self qui est retenu / recopié à  la création du block et non la variable contenant mapropriete, dans ce cas) sans rien avoir à  gérer de +. Tu n'as même pas besoin de __block dans ce cas.



    Aurais-tu un morceau des code à  nous montrer pour qu'on voit ce que tu as essayé de faire et qu'on voit ce qui fait que d'après toi ça ne marche pas ?
  • Ou sinon tu peux regarder une vidéo des CocoaHeads qui traite ce sujet : http://cocoaheads.fr/2012/04/slides-rennes-9-gestion-memoire-du-debutant-a-lexpert/
  • Merci de vos réponses !



    Il semblerait que j'étais fatigué hier soir... Après avoir refait quelques tests j'arrive en effet aux résultats attendus !



    Cependant, dans le cas des blocks avec GCD, dois-je utiliser un mécanisme de mutex pour la modification de valeur de variables ? Pour mes blocks de code à  verrouiller, me conseillez-vous plutôt les NSLock ou les @synchronize ?



    Merci.
  • AliGatorAliGator Membre, Modérateur
    Alors je dirais ni l'un ni l'autre :
    • Normalement avec GCD bien souvent tu n'as pas besoin de locks explicites comme NSLock ou mutex. C'est justement toute la beauté de GCD.
    • Le fait que les blocs soient dépilés sur une queue les uns après les autres dans l'ordre dans lesquels ils ont été empilés aide.
    • Lorsque tu empiles différents blocs sur une queue "serial", il va attendre que le bloc précédent soit fini d'exécuter pour dépiler le suivant, donc tu ne risques pas d'avoir de conflit d'accès à  des variables communes aux deux blocs. Il faut potentiellement faire attention aux variables partagées à  la fois par des blocs empilés sur une queue serial et par d'autres threads s'exécutant en parallèle (on retombe alors dans le cas cité au point suivant)
    • Lorsque tu empiles des blocs sur une queue "concurrent", les blocs peuvent être exécutés en parallèle (c'est le but de ce type de queue). Dans ce cas il faut s'assurer de l'atomicité des accès en effet. Pour cela, je te conseille de prendre l'habitude d'accéder aux variables d'instance via les propriétés (d'ailleurs je te conseille d'abandonner totalement les variables d'instances déclarées explicitement et de ne déclarer que des @property) et pour les variables sensibles de déclarer lesdites @property en atomic (de ne pas mettre "nonatomic") devrait aider puisque cela va implicitement utiliser un @synchronise sur son accès.
    • Pour le reste tu ne devrais pas avoir trop de soucis dans la majorité des cas : les variables auxquelles tu accèdes directement (par opposition aux variables accédées à  travers une propriété d'un objet ou par référence) comme les variables locales par exemple sont const-copiées à  la création du block, donc l'original n'est jamais touché. Pour les variables dépendant d'autres objets, accédées par référence à  un objet de base, tu utilises les propriétés (self.maprop) qui va utiliser le getter, et si le getter est bien déclaré comme atomic, tu n'auras pas de soucis non plus


    Au final dans l'ensemble en général quand tu utilises GCD tu as peu à  te soucier de cela. Je dis pas que tout est automatique, en effet il faut y faire attention et c'est une bonne habitude de ta part. Mais cela est grandement simplifié tout de même. Du moment que tu utilises à  bon escient dispatch_sync ou dispatch_async en fonction du besoin, et une dispatch_queue concurrent ou serial selon les contraintes voulues, et que tu passes par des property atomic pour être sûr.



    Après il y a des cas plus alambiqués où tu peux avoir besoin de mutex ou de locks en effet. Mais dans ce cas, utilise ceux fournis par GCD, qui sont bien plus rapides (et en plus sont des double-check-locks donc moins consommateurs) que des NSLocks ou autre @synthesize. Je te laisse potasser la doc pour ces cas-là , qui restent assez rares en pratique de mon expérience.



    PS : Autres lectures intéressantes en plus du Concurrency Programming Guide & co : http://www.mikeash.com/pyblog/friday-qa-2009-08-28-intro-to-grand-central-dispatch-part-i-basics-and-dispatch-queues.html
  • Sébastien M.Sébastien M. Membre
    septembre 2012 modifié #7
    Wahou ! C'est de la réponse !



    Concrètement, je suis en train de coder une encapsulation asynchrone des sockets en Objective-C. Je sais que cela a déjà  été fait de nombreuses fois, mais c'est dans l'objectif d'apprendre de nouveaux concepts.



    Lorsque je veux écrire une donnée sur une socket, mon thread principale ajoute un NSData dans un NSMutableArray. Dans un autre thread/une autre queue, une boucle vérifie le contenu du NSMutableArray : s'il n'est pas vide, il écrit sur la socket.



    Du coup, il suffirait de déclarer le NSMutableArray comme atomic grâce a un @property et cela fonctionnerait correctement, c'est bien cela ?

    Cependant, cela ne permettrait pas alors à  n'importe quel autre objet d'accéder directement à  mon NSMutableArray ? Ne vaut-il pas mieux alors mettre manuellement le @synchronise ?



    Merci !
  • MalaMala Membre, Modérateur
    septembre 2012 modifié #8
    Il peut être bon de faire attention à  l'impact du type de lock utilisé sur les perfs. En voici l'illustration...


    2012-09-11 11:56:28.477 LockTest[67506:1307] @syncronized test

    2012-09-11 11:56:32.988 LockTest[67506:1307] Took 4.50677 seconds to complete



    2012-09-11 11:56:32.989 LockTest[67506:1307] NSLock test

    2012-09-11 11:56:37.072 LockTest[67506:1307] Took 4.08219 seconds to complete



    2012-09-11 11:56:37.073 LockTest[67506:1307] pthread_mutex test

    2012-09-11 11:56:39.496 LockTest[67506:1307] Took 2.42226 seconds to complete



    2012-09-11 11:56:39.497 LockTest[67506:1307] OSSpinLock test

    2012-09-11 11:56:40.063 LockTest[67506:1307] Took 0.564779 seconds to complete
  • Sébastien M.Sébastien M. Membre
    septembre 2012 modifié #9
    Ah oui quand même !

    Donc globalement, il vaut mieux utiliser les OSSpinLock. Les autres ont-il des avantages ?



    Dans ton code tu inclus :
    #import &lt;libkern/OSAtomic.h&gt;<br />
    




    Vu le nom, je suppose que c'est quelque chose d'assez bas niveau. Celui-ci ne fonctionnerait-il pas uniquement sur OSX (dans le man, j'ai une référence à  Darwin) ?
  • iOS et OS X ont la même base. À savoir un noyau Mach dans un système basé sur Free BSD. C'est cet ensemble que l'on appelle Darwin. iOS et OS X rajoutent de nouvelles couches par dessus Darwin. Certaine, comme Core Foundation, sont communes et d'autres différentes, surtout si ça concerne l'interface graphique (AppKit et UIKit).



    Le coe“ur du système est donc le même (sauf, bien entendu, si des nouveauté sont introduites dans OS X et que le noyau d'iOS n'a pas encore été mis-à -jours.)
  • Merci pour vos réponses.



    Il faut donc juste faire attention à  ne pas utiliser cela si on compile de l'Objective-C sur un linux ! ;-)
  • Finalement, j'ai opté à  la fois pour les OSSpinLock et les @property en fonction des cas.



    Merci à  tous pour vos réponses.
Connectez-vous ou Inscrivez-vous pour répondre.