Variables croisées: strong ou weak?

Bonsoir,


 


Avant toute chose, voici ce que je compte faire:


 


Dans la fenêtre d'un document Core Data, j'ai une liste standard de NSManagedObjects gérée de la manière habituelle (via NSArrayController). Dans une autre NSView située dans la même fenêtre, je compte insérer des subviews liées aux MO -- via un NSViewController -- pour en donner une représentation visuelle (mes objets ont une position, une taille, une couleur, etc.)


 


Mon intention est d'attribuer une propriété temporaire (transient) aux MO qui serait cette subview. Inversement, la subview aurait un lien vers le MO qu'elle représente: de cette façon, un clic sur la subview sélectionnerait le MO dans la liste, et réciproquement.


 


Ma question est la suivante: j'ai entendu parler des "retain cycles" et je souhaiterais éviter des zombies. Pour cela, comment lier les deux objets, sachant que les subviews sont temporaires (crées à  la lecture du fichier et "oubliées" lors de la fermeture).


 


Je travaille avec ARC.


 


Merci du coup de main!


Mots clés:

Réponses

  • AliGatorAliGator Membre, Modérateur
    Hello

    Tout est expliqué ici.

    En bref, tu fais bien de te poser la question, et pour ton problème il faut que tu détermines qui pour toi est le plus logique d'être "le parent" et l'autre "l'enfant", l'entité parente va "posséder"/"contenir" l'entité enfant (strong reference) alors que l'entité enfant va "faire référence" à  l'entité parent (weak reference).

    Bonne lecture.
  • Pour moi dans ce cas là  les deux liaisons serait en weak puisque chacun a un parent de son côté.


  • Mmmh. FKDEV souligne le côté ambigu de ma conception: une sorte de relation d'égal à  égal...


     


    Cependant je compte utiliser des NSManagedObjects tels quels, sans dérivation. Je suppose que Core Data utilise le défaut ARC pour les propriétés de ses objets (strong). Je pense donc utiliser une liaison weak NSView-->NSManagedObject. Je ne risque pas, de cette manière, de semer le chaos dans le NSManagedObjectContext...


     


    De fait, si je suis la logique d'AliGator (et après avoir RTFM) mon objet "possède" une représentation visuelle (entre autres choses) et ma représentation visuelle "fait référence à " un objet, elle ne le possède pas (si tant est qu'un NSManagedObject puisse être "possédé" par autre chose que son NSManagedObjectContext...)


     


    Merci à  tous deux, je vais tester (en activant les zombies pour être sûr)...


  • Je crains que ce ne soit pas aussi simple.


     


    Un NSManagedObject est une sorte de proxy sur les instances d'entité gérées dans un store Core Data.


    Les instances d'entités sont effectivement la "propriété" du contexte Core Data mais pas les instances de NSManagedObject.


    Je ne pense pas que ça marche de n'avoir que des références weak sur un managed object.


     


    Cela ne me choque pas qu'un contrôleur définisse une référence forte sur la partie de modèle qu'il manipule.


    Et cela ne me choque pas non plus que deux contrôleurs aient tout deux une référence forte sur un même objet. Ce qu'il faut éviter ce sont les cycles. Si les références fortes vont toujours des contrôleurs vers le modèle il n'y a pas de cycle.


  • AliGatorAliGator Membre, Modérateur
    Y'a quand même un truc qui me choque dans ta question : tu souhaites finalement relier directement (voire mélanger) le modèle (ton MO) et la vue (ta NSView). ça me semble pas très MVC tout ça...
  • FKDEVFKDEV Membre
    septembre 2013 modifié #7

    Moi le fait que ce ne soit pas MVC ne me choque pas trop, car si j'ai bien compris, c'est une vue spécialisée qui n'a comme rôle que de donner une représentation graphique d'un objet du model.


    M'enfin après il faudrait voir le code...


     


    En fait weak/strong ce n'est pas très important si tu n'as pas de retain cycle, mais c'est quand même bien de savoir qui est responsable de tes objets (je pense que c'est ce que voulait dire Ali par "parent"). 


    Si on veut mettre du strong, je dirais que c'est la vue qui doit avoir une référence strong vers l'objet. Car elle a besoin d'un objet du model pour afficher quelque chose donc elle doit garder une référence fiable vers l'objet du model. 


    Le model n'a pas besoin de la vue pour exister donc, non seulement il peut n'avoir qu'une référence weak, mais en fait il ne devrait même pas avoir de référence du tout vers la vue. C'est la vue qui devrait observer ton objet via KVO.


     


    Le modèle est ce qu'il y a de plus pure dans une appli, idéalement, tu dois pouvoir le réutiliser ailleurs sans modifier les fichiers.


    Par exemple, dans des tests unitaires ou dans un outil en ligne de commande.


  • berfisberfis Membre
    septembre 2013 modifié #8


    Y'a quand même un truc qui me choque dans ta question : tu souhaites finalement relier directement (voire mélanger) le modèle (ton MO) et la vue (ta NSView). ça me semble pas très MVC tout ça...




    Si, en fait je passe par un NSViewController entre le MO et sa représentation visuelle, il me semble respecter le pattern.


     


    Le souci que j'ai, c'est comment éviter les relations entre objets si:


    a. J'ajoute un MO via l'arrayController


    b. Je déplace la vue (mouseDragged) et je souhaite mettre à  jour les coordonnées du MO.


     


    A la fin du déplacement (mais je pourrais le faire tout au long de celui-ci) la vue peut envoyer un [self.itsObject setValue... forKey...] à  l'objet directement (ça c'est pas MVC);


     


    La vue pourrait envoyer une notification. Là  on n'a plus de couplage, mais ce côté "Au cas où ça intéresse quelqu'un, ma position a changé" expédié à  tout le monde alors que seul un destinataire est concerné, ça me chiffonne un peu;


     


    Ou alors, pour rester MVC, la vue informe son contrôleur qu'elle a été cliquée/déplacée, le contrôleur de vue informe l'arrayController qu'il doit modifier les valeurs de sa sélection courante. C'est bien joli, mais sans référence modèle <--> vue, ça tient de l'équilibrisme, non?


     


    Il doit bien exister un moyen de donner une représentation visuelle d'un NSManagedObject et d'interagir avec elle sans enfreindre un pattern, non?


  • AliGatorAliGator Membre, Modérateur
    Avec un @protocol servant de DataSource, comme pour les UITableView et les UITableViewCells ?
  • Quand tu fais :


    itsObject setValue: forKey:,


    tu utilises déjà  un pattern delegate en quelque sorte (basé sur le protocol NSObject)

    Sauf que le protocol utilisé est trop generique.


    Il faudrait des methodes plus specifiques type :

    didMoveToPoint:
  • J'évite toujours la DataSource si je peux. Et... je n'ai jamais utilisé de protocole. Ok, ok, RTMF...


     


    Et pour fixer les idées, voilà  à  quoi ça ressemble actuellement.


  • AliGatorAliGator Membre, Modérateur

    Quand tu fais :

    itsObject setValue: forKey:,

    tu utilises déjà  un pattern delegate en quelque sorte (basé sur le protocol NSObject)

    Heinnn ?

    Ca c'est le pattern KVC.

    Rien à  voir avec le pattern delegate, qui, lui, consiste à  déléguer à  un autre objet (celui que tu mets en delegate, donc) la responsabilité de répondre à  des questions ("-(BOOL)shouldDoSomething", ...) ou de fournir le code à  exécuter / comportement à  avoir dans certaines situations ({here is what to do when...} "yourObject:didMoveToPoint:"), bref d'affiner le comportement de l'objet principal.
    Le pattern delegate permet de déléguer des choses à  un autre objet pour éviter de sous-classer juste pour spécifier / ajuster 2-3 comportements. Tu imagines s'il fallait sous-classer UITextField juste pour surcharger une méthode "-textChanged" avec un comportement différent, et ce avec du coup autant de sous-classes que tu aurais de TextFields dans ton appli puisque typiquement chacun de ces TextFields aurait besoin d'un code différent selon le contexte ? Ca serait la galère. Le pattern delegate est là  pour éviter ça et déléguer cette responsabilité (de savoir quoi faire quand ton texte change) à  un autre objet (ton ViewController par exemple), sans être obligé de créer une classe dédiée juste pour avoir un code spécifique pour textChanged.

    Alors que setValue:forKey:, c'est du KVC, donc un pattern d'abstraction très générique. Certes il est défini dans un @protocol, tout comme pour les delegates on utilise l'outil de langage que sont les @protocol aussi pour définir un contrat d'interface entre l'objet et son délégué. Mais l'utilisation de ce même outil de langage que sont les @protocol est à  peu près le seul point commun entre ces 2 design patterns.
  • AliGatorAliGator Membre, Modérateur

    J'évite toujours la DataSource si je peux. Et... je n'ai jamais utilisé de protocole. Ok, ok, RTMF...
     
    Et pour fixer les idées, voilà  à  quoi ça ressemble actuellement.

    Les @protocol, c'est vraiment pas sorcier. C'est juste déclarer un contrat d'interface, autrement dit donner une liste de méthodes. C'est un peu comme une @interface mais sans @implementation, pour faire simple. Tu fournis juste la liste des méthodes que les objets voulant se conformer au protocole devront implémenter. Mais ça n'impose aucune contrainte de hiérarchie de classes.

    Par exemple, n'importe quelle classe peut se conformer au protocole NSTableViewDataSource. En général c'est ton NSViewController qui s'y conforme, mais ça peut très bien être un NSObject. Ou n'importe quoi d'autre. Du moment qu'il sait répondre aux questions qu'on lui a listé (qu'il implémente les méthodes listées dans le @protocol), on s'en fout de savoir qui c'est.

    Donc typiquement tu vas déclarer un @protocol listant les questions à  poser à  ton objet modèle, autrement dit des getters pour accéder aux informations de tes objets modèles. Et tes NSView vont avoir une "@property id<TonProtocoleDataSource> representedObject" par exemple (que tu affecteras à  ton objet modèle) et tes NSView vont pouvoir demander à  ce representedObject les valeurs de ses diverses propriétés dont il a besoin pour l'affichage. Comme ça, ta NSView n'est pas fortement liée à  ton objet modèle, elle est juste liée par un contrat d'interface, mais à  l'autre bout ça peut être n'importe quoi qui sait répondre aux questions. Un NSObject, un objet CoreData, un objet custom, ...

    D'ailleurs ce terme de "representedObject" que je viens de sortir pour ces explications, je me demande si je ne l'ai pas déjà  vu dans les API Cocoa (de NSTableView ? de NSViewController ?), en particulier pour les bindings pour justement pouvoir binder facilement un objet (par exemple un objet CoreData) à  un controller et sa vue ? Ce qui tendrait à  aller dans mon sens puisque tu aurais limite déjà  ce pattern de préparé / utilisé dans Cocoa...
  • FKDEVFKDEV Membre
    septembre 2013 modifié #14


    J'évite toujours la DataSource si je peux. Et... je n'ai jamais utilisé de protocole.

    C'est pas bien.


    Plusieurs remarques :

    La notion de datasource n'est rien d'utre qu'un protocol destiné à  fournir des donnees. Le delegate est un autre type de protocol pour un usage different.

    La notion de protocol est donc fondamentale en programmation objet, dans d'autres langage cela s'appelle interface ou classe abstraite, etc


    C'est une notion qui consiste à  séparer les services rendus par un objet, ddu type de l'objet.

    Quand tu utilises un objet qui repond a un certain protocol, tu veux juste qu'il implemente telles ou telles fonctions, tu n'as pas besoin de savoir quelle est sa classe. Tu peux donc facilement remplacer cet objet par un autre du moment que les deux implementent le bon protocol.


    C'est un peu comme si tu avais besoin d'un traducteur français-chinois pour une reunion, tu veux juste quelqu'un qui parle hyper bien le français et le chinois, tu t'en fous de savoir s'il ou elle aime les épinards ou s'il ou elle fait du vélo le week end.


    Autrement dit, c'est un moyen de relier deux objets avec un minimum d.adherence possible. Cela permet une meilleur maintenance et une meilleur reutilisabilité parce que tu sais exacement pourquoi (pour quelles methodes) les objets sont reliés entre eux.
  • FKDEVFKDEV Membre
    septembre 2013 modifié #15


    Heinnn ?Ca c'est le pattern KVC.Rien à  voir avec le pattern delegate, qui, lui, consiste à  déléguer à  un autre objet (celui que tu mets en delegate, donc) la responsabilité de répondre à  des questions ("-(BOOL)shouldDoSomething", ...) ou de fournir le code à  exécuter / comportement à  avoir dans certaines situations ({here is what to do when...} "yourObject:didMoveToPoint:"), bref d'affiner le comportement de l'objet principal.Le pattern delegate permet de déléguer des choses à  un autre objet pour éviter de sous-classer juste pour spécifier / ajuster 2-3 comportements. Tu imagines s'il fallait sous-classer UITextField juste pour surcharger une méthode "-textChanged" avec un comportement différent, et ce avec du coup autant de sous-classes que tu aurais de TextFields dans ton appli puisque typiquement chacun de ces TextFields aurait besoin d'un code différent selon le contexte ? Ca serait la galère. Le pattern delegate est là  pour éviter ça et déléguer cette responsabilité (de savoir quoi faire quand ton texte change) à  un autre objet (ton ViewController par exemple), sans être obligé de créer une classe dédiée juste pour avoir un code spécifique pour textChanged.Alors que setValue:forKey:, c'est du KVC, donc un pattern d'abstraction très générique. Certes il est défini dans un @protocol, tout comme pour les delegates on utilise l'outil de langage que sont les @protocol aussi pour définir un contrat d'interface entre l'objet et son délégué. Mais l'utilisation de ce même outil de langage que sont les @protocol est à  peu près le seul point commun entre ces 2 design patterns.

    .


    Je n'ai pas écrit que KVC est un pattern delegate.


    Je lui explique (peut-être maladroitement) qu'il est en train de faire un pattern delegate avec le mauvais outil (KVC).

    Que la demarche est bonne mais qu'il faudrait utiliser un protocol plus specifique.
  • Bonsoir,


     


    Puisque le sujet a évolué vers les patterns Cocoa, j'aurais une petite question pour vous :). La plupart du code que j'ai lu et que j'ai écrit aussi utilise les fameux protocoles formels ou informels mais généralement juste pour les patterns Délégation/DataSource. 


     


    Utilisez-vous les protocoles, pas pour la délégation/Data source, dans vos programmes de tous les jours ? Merci. J'ai l'impression que c'est une pratique beaucoup plus Java que Cocoa, mais quand je regarde les classe Cocoa y en a des tonnes ;)


  • AliGatorAliGator Membre, Modérateur
    Perso je l'utilise pas mal oui, pour mettre en place l'abstraction de mes divers modules.

    Après faut être honnête y'en a pas non plus à  tout va, partout dans mon code. Une grande partie ça reste pour délégate ou dataSource car c'est le plus fréquent cas d'usage rencontré. Mais quand j'ai un besoin d'abstraction parce que ça à  du sens oui je n'hésite pas. En tout cas c'est fréquent que je déclare mes protocoles.


    Par exemple j'ai un WebService générique dans un de mes framework à  qui on peut affecter un id<OHAuthenticationManager>. Je m'en fous de quelle classe fournit ça, tout ce que je lui demande c'est savoir me fournir un token. Première abstraction, pour je pas lier le WS et la couche d'authent.

    J'ai aussi un id<OHAuthenticationDialog> à  qui je peux demander d'afficher de quoi rentrer un login/mdp, me les retourner via un block quand l'utilisateur les a saisis... deuxième abstraction, permettant de délier la couche service et gestion des requêtes de la couche UI.


    (Bon en vrai ce ne sont pas les vrais exemples j'ai simplifié hein)
  • colas_colas_ Membre
    septembre 2013 modifié #18

    @Berfis


     


    Peut-être que dans ce cas, vouloir éviter les datasource (je comprends ton point de vue) revient à  se compliquer la vie ? Quand on est habitué, je peux t'assurer qu'implémenter un datasource c'est de la gnognote.


     


    Cependant avec ce qui suit, il semble que tu aies juste besoin d'avoir un délégué (ou une notification).


     


    En partant du principe que tu as une méthode myViewDidMove, pourquoi ce qui suit ne marche pas ?



    - myViewDidMove:theView{
    [myView.myObject setTheNewValues]
    [tableView reloadData] ;
    }

    Et, dans object, tu n'aurais pas un lien strong vers ta vue ou (mieux) juste un lien fort vers les infos (genre : x, y, diameter...) qui comptent (une nouvelle Entity InfoOfView, avec une méthode getInfoFromView). Dans l'autre sens, un lien weak de ta vue vers l'objet qu'elle représente.


     


    En fait, dans ce cas, implémenter un delegate revient (est équivalent) à  lancer une notification.


    Je comprends ta réticence à  lancer une notification, mais je crois que c'est comme ça que l'on sait si on change la sélection d'un tableView (par exemple) : donc, pas si contournable que ça, si Apple le fait.


     


    En espérant t'avoir aidé...


  • CéroceCéroce Membre, Modérateur

    Utilisez-vous les protocoles, pas pour la délégation/Data source, dans vos programmes de tous les jours ? Merci. J'ai l'impression que c'est une pratique beaucoup plus Java que Cocoa, mais quand je regarde les classe Cocoa y en a des tonnes ;).

    Je réponds oui, comme Ali.
    Ensuite, comprends que l'héritage ou l'héritage d'interface n'apparait qu'avec un certain niveau de complexité. Ce n'est donc pas moins courant qu'en Java; en fait, les langages sont tellement proches que ce qui est vrai pour l'un l'est souvent pour l'autre.
  • FKDEVFKDEV Membre
    septembre 2013 modifié #20

    Il faut utiliser des protocoles quand cela a du sens. C'est-à -dire pas si souvent que ça.


     


    Dans ton cas, si tes objets modèles sont des corps célestes et tes vues différentes représentations de ces corps célestes (graphiques et textuelles), alors selon mon point de vue tu n'as pas besoin de protocols entre les deux.


     


     


    Tes models doivent rester propres et réutilisables sur n'importe quelle plateforme qui supporte cocoa.


    Leurs caractéristiques sont masses, vitesse, apsides, etc (je n'y connais rien...)


     


    Tes objets models ne doivent pas avoir de pointeurs vers tes vues. Tu peux imaginer une appli où tu aurais simultanément trois représentations de tes corps célestes : un tableau, une vue en 2D et une vue en 3D. Ces représentations sont différentes projections de tes objets modèles. Tes objets modèles n'ont pas besoin de savoir que ces représentations existent.


    Tu pourrais imaginer avec les mêmes objets de créer un outil en ligne de commande pour calculer les orbites de tes planètes sur 1000 ans par exemple. Dans ce cas pas de représentation graphiques, mais tu utilises les mêmes objets modèles. 


     


    Tes vues en revanche doivent avoir un pointeur vers tes objets, je pense qu'il peut être fortement typé. Par exemple un pointeur vers une classe de base ObjetCeleste (dont Etoile, Planete et eventuellement Asteroid seront des dérivés).


    Pas la peine de mettre un protocol ici, tu ne vas pas réutiliser tes vues pour représenter autres choses que des planètes et des étoiles.


     


    C'est un cas où, à  mon avis, il ne faut pas passer par du MVC pour faire communiquer tes vues et tes objets modèles. En revanche tu peux passer par le controller pour établir la liaison entre chaque vue et chaque objet du modèle. Une fois la liaison établie, la vue utilise son pointeur vers ObjetCeleste pour récupérer les paramètres de vitesse, etc.


     


    Si la vue a besoin de savoir si l'objet céleste est une planète ou un soleil, alors il faut éviter de faire trop de tests du type "if ([objet isSoleil])" dans ta vue mais plutôt dériver la vue en vue plus spécialisée.


    NSView <- ObjetCelesteView <- SoleilView 


     


    Pour ton tableau :


    NSTableViewCell <- ObjetCelesteCellView


     


    Si la vue a besoin d'être informée des changements des caractéristiques des objets models, alors elles utilisent les KVO (pattern observer).


    Le pattern observer, c'est un bon moyen de créer une liaison implicite d'un objet model vers un objet vue, sans "salir" l'objet model avec un pointeur vers la vue.


     


     


    En ce qui concerne la réutilisabilité :


    -le model est réutilisable partout où il y a du cocoa (voire sur plus de plateformes si tu crées un objet C++ "décoré" par un objet cocoa, mais c'est sans doute too much)


    -la vue graphique, réutilisable sur OSX et iOS avec quelques ajustements mineurs au niveau des API graphiques (NSView/UIView, NSColor/UIColor, etc). 


    -la vue tableau, tu peux éventuellement prévoir une réutilisation entre OSX et iOS, si tu choisis un view-based NSTableView sur OSX (c'est pas forcément rentable).


    -les controllers et les vues parents : non réutilisables, donc y mettre uniquement le code glue qui permet de créer et d'établir les liens entre les objets ci-dessus. Si tu dois créer des protocols type datasource ou delegate, ça peut être à  ce niveau éventuellement pour faire communiquer tes controllers avec l'objet qui va lui fournir les objets models, à  voir...).


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