Modèle, Vue et Undo

Bonsoir,


 


J'ai un problème devant lequel je me voile la face depuis trop longtemps. Pour l'instant, aucun testeur ne l'a fait remonter, mais un jour où l'autre quelqu'un va dégoupiller la grenade...


 


J'ai des NSManagedObjects qui ont une représentation à  l'écran. C'est par cette représentation uniquement que l'utilisateur crée, supprime et positionne les objets.


 


Ma sous-classe de NSManagedObject possède une propriété transitoire (weak) vers la sous-classe de NSView qui représente l'objet. La vue a une propriété (strong) vars l'objet qu'elle représente.


 


A l'ouverture du document, un contrôleur instancie les vues selon les propriétés (persistantes) de l'objet: coordonnées, couleur, etc. et les installe dans une superview. L'utilisateur peut déplacer les vues, qui mettent les coordonnées de l'objet à  jour. S'il supprime une vue, l'objet correspondant est détruit. A la fermeture, les objets sont sauvegardés et les vues détruites, sans la moindre fuite.


 


Tout va pour le mieux dans le meilleur des mondes, jusqu'au moment où l'utilisateur, après avoir détruit un objet... décide d'annuler son action. Le mécanisme de Core Data va fonctionner à  merveille pour l'objet, qui va "ressusciter" comme par miracle... mais sans vue correspondante. A partir de ce moment-là , l'application se trouve dans un état instable: le nombre d'objets ne correspond plus à  ce que l'on voit sur l'écran, et l'objet possède maintenant un pointeur sur nil. Si rien ne crashe, l'objet va réapparaà®tre à  la prochaine réouverture du document " au grand désarroi de l'utilisateur.


 


Question: comment répercuter le Undo du Modèle vers la Vue? Une idée que je pourrais mettre en oeuvre et que je balance au plus vite une nouvelle version qui "résout des problèmes et améliore la stabilité"?


 


D'avance merci!


«1

Réponses

  • Il faut définir la méthode awakeFromSnapshotEvents: sur les objets gérés, elle sera appelée avec NSSnapshotEventUndoDeletion lorsque l'objet sera recréé. C'est le bon endroit pour lancer la reconstruction de la vue de cet objet, par exemple par une notification.


  • CéroceCéroce Membre, Modérateur
    Une autre solution serait de respecter le MVC, non ?
  • Joanna CarterJoanna Carter Membre, Modérateur

    Une autre solution serait de respecter le MVC, non ?




    Bah oui. Avoir les réferences pour les vues dans le modèle ou vice versa, c'est un des pêchés mortels et ce n'est pas du tout nécessaire dans ce cas là 
  • FKDEVFKDEV Membre
    mars 2015 modifié #5

    Ma sous-classe de NSManagedObject possède une propriété transitoire (weak) vers la sous-classe de NSView qui représente l'objet. La vue a une propriété (strong) vars l'objet qu'elle représente.



     


    Le pointeur strong dans la vue vers le modèle ne me choque pas. En revanche, tu dois savoir que ta vue devient très spécifique à  ton modèle et ne sera donc pas réutilisable.


    Dans ce cas chercher à  respecter le MVC est juste du dogmatisme sans bénéfice réel.


     


    La relation inverse par contre ne devrait sans doute pas être aussi spécifique, tu devrais passer par une couche d'abstraction pour éviter que le modèle ait un pointeur vers un NSView*.


    Tu as plusieurs choix:


    -Remplacer le pointeur vers NSView par un pointeur vers un protocol (id<MonProtocol>).


    -Utiliser les KVO : la vue observe les propriétés qui l'intéresse dans le modèle (plus ouvert mais moins explicite)


     


    En ce qui concerne ton problème, puisque c'est ton controller qui créée initialement les vues et établie les liens vers les objets du modèle. C'est lui qui doit également faire se travaille si un objet du modèle "revit". Le problème est donc juste de savoir comment prévenir le controller de la résurrection d'un objet et jpimpert t'a donné la solution.


     


    Dans le controller tu dois pouvoir découper ton code de manière à  ce que la création initiale d'un objet soit équivalente à  la création via undo. Je veux dire : un objet arrive, le controller crée la vue correspondante, quel que soit son origine. 


  • CéroceCéroce Membre, Modérateur


    Le pointeur strong dans la vue vers le modèle ne me choque pas. En revanche, tu dois savoir que ta vue devient très spécifique à  ton modèle et ne sera donc pas réutilisable.


    Dans ce cas chercher à  respecter le MVC est juste du dogmatisme sans bénéfice réel.




    Non, ce n'est pas du dogmatisme. Si le modèle reçoit une commande d'undo, ses valeurs vont changer, mais la vue ne sera pas au courant. Alors que si le contrôleur sert d'intermédiaire, c'est lui qui va recevoir la commande undo. Il pourra alors exécuter l'undo sur le modèle puis demander à  la vue de se mettre à  jour.


     


    J'ai assez fait le malin à  vouloir contourner le MVC " par exemple, parce que je ne voulais pas créer un type intermédiaire pour nourrir la vue " pour savoir que c'est une mauvaise idée. On arrive vite à  la solution que la vue fait du KVO sur le modèle pour savoir s'il a changé. Et là  c'est la cata, avec de belles exceptions parce que le modèle a disparu, mais la vue l'observe toujours, et de grosses dépendances entre la vue et le modèle qui rendent tout ça difficile à  déboguer.

  • FKDEVFKDEV Membre


    Non, ce n'est pas du dogmatisme. Si le modèle reçoit une commande d'undo, ses valeurs vont changer, mais la vue ne sera pas au courant. Alors que si le contrôleur sert d'intermédiaire, c'est lui qui va recevoir la commande undo. Il pourra alors exécuter l'undo sur le modèle puis demander à  la vue de se mettre à  jour.




     


    Pour les commandes globales qui touchent à  la collection d'objets, je suis d'accord, j'ai même proposé de remonter l'info au controller.


    C'est logique : le controller connait la collection de vues et la collection de modèles, donc les commandes qui touchent aux collections doivent être gérées par le controller (ajouter, supprimer, réordonner).


    Et encore, ça pourrait se discuter si le modèle gère lui-même la collection (par exemple, une playlist de tracks).


     


    Je voulais juste exprimer que je ne vois pas pourquoi interdire un lien direct pour des échangent simples comme "afficher un fond vert" quand le model est dans l'état OK et un fond rouge s'il est dans l'état ERREUR, ou même dans l'autre sens, switcher l'état quand on tape sur la vue.


     


    Une fois que le lien a été établi, je ne vois pas l'avantage de forcer à  passer par le controller pour ce type de code.


    Par exemple, quand dans une fonction du controller type cellForRowAtIndexPath:, on a 20 lignes du type :


    cell.XXXX = model.YYY


    Je me dis qu'il y a un problème avec le MVC dans ce cas.


    Et c'est encore pire quand on met un bouton dans une cellule et qu'on doit repasser par le controller pour faire l'action.


     


    Pour moi, les KVO sont une bonne solution quand on n'a pas accès aux sources, sinon il vaut mieux faire l'équivalent de manière plus explicite via un protocole/delegate ou son propre mécanisme d'observer.

  • PyrohPyroh Membre

    Il faut m'expliquer en quoi ce cas de figure diffère d'une NSTableView...


     


    Je m'explique :


     - Tu as une collection.


     - Tu as une vue qui affiche une collection, qui gère la selection et qui permet add/delete.


     - Tu utilise CoreData.


     


    Voilà  ce qu'on pourrait y voir :


     - Le model est géré par CoreData dans un NSManagedContext.


     - La collection est gérée par un NSArrayController, 3 avantages:


         + UndoManager gratos.


         + Add/Delete sans se prendre la tête.


         + Une demi tonne de code en moins.


     - La vue est plus complexe il te faut une NSView principale qui crée et affiche des NSView subalternes en fonction de la collection. 


     


    Pour connecter le tout tu binde, on est sous OSX c'est la manière la plus simple de le faire :


     - Tu binde la collection sur la NSView principale et en suite tu binde les prop de chaque objet sur les NSView subalternes. 


     - Tu ne manipule pas la collection directement dans la view mais via le controller pour les add/delete.


     - Tu propage proprement les changement sur les bindings.


     


    ça fait pas mal de boulot à  refaire mais ça plantera pas. (Documente toi beaucoup sur les NSObjectController et descendants).

  • Joanna CarterJoanna Carter Membre, Modérateur
    Merci pour le lien sur comment implémenter un binding, je l'avais perdu :)
  • CéroceCéroce Membre, Modérateur


    Je voulais juste exprimer que je ne vois pas pourquoi interdire un lien direct pour des échangent simples comme "afficher un fond vert" quand le model est dans l'état OK et un fond rouge s'il est dans l'état ERREUR, ou même dans l'autre sens, switcher l'état quand on tape sur la vue.




    Je peux te donner au moins deux raisons:


    - la vue n'est alors pas réutilisable, puisque sa couleur dépend d'un état du modèle.


    - la vue devient concernée par le modèle, alors que son rôle est de savoir comment s'afficher.


     


    Et là , tu me répondras que certes, mais que si je fais du MVC complet, c'est le contrôleur qui deviendra non-réutilisable, et que je ne fais que déplacer le problème. (À vrai dire, nous savons que le contrôleur est rarement réutilisable, et il va falloir tendre à  le rendre le plus léger possible, en déplaçant le maximum de logique dans le modèle; ne serait-ce que parce que c'est beaucoup plus facile à  tester).


     


    Que dire, si ce n'est qu'aucune solution n'est parfaite et qu'il s'agit d'un compromis. Dans mon expérience, coller au MVC exige d'avantage de code, mais rend l'architecture moins complexe, parce que la "surface d'échange" est moins large. C'est donc une stratégie mauvaise à  court terme, mais bonne sur le long terme.

  • AliGatorAliGator Membre, Modérateur
    +1 Avec Ceroce.
  • Joanna CarterJoanna Carter Membre, Modérateur
    mars 2015 modifié #12
    Idem pour moi
  • berfisberfis Membre
    mars 2015 modifié #13

    Hé bien... merci pour la diversité, la pertinence et la diligence de vos réponses ! Il y a à  creuser pour chacune d'elles... :)


     


    En fait, actuellement j'ai une structure de ce type.


  • Joanna CarterJoanna Carter Membre, Modérateur

    Le NSArrayController n'est pas nécessaire si tu ne prévois pas connecter les vues avec bindings. lis-toi le message de Pyroh


  • @Joanna: Pyroh parle bien d'un ArrayController, puisqu'il mentionne le add, remove et undo. En plus, NSArrayController a un comportement particulier (je n'ai pas trouvé la doc, ça doit être un des ces trucs "magiques") quand il gère des Entities Core Data en lieu et place de NSMutableDictionaries.


     


    Je creuse encore. Ce n'est pas parce que "ça marche" que c'est propre. À cela vient s'ajouter le fait que les représentations des objets doivent être crées, détruites etc. avec un dispatch, parce que Core Data attend la fin de la boucle pour actualiser son modèle.


     


    La magie a son prix.


  • FKDEVFKDEV Membre
    mars 2015 modifié #16

    Je peux te donner au moins deux raisons:

    - la vue n'est alors pas réutilisable, puisque sa couleur dépend d'un état du modèle.

    - la vue devient concernée par le modèle, alors que son rôle est de savoir comment s'afficher.

    (...)

    (À vrai dire, nous savons que le contrôleur est rarement réutilisable, et il va falloir tendre à  le rendre le plus léger possible, en déplaçant le maximum de logique dans le modèle; ne serait-ce que parce que c'est beaucoup plus facile à  tester)




    En gros tu me dis que tu préfères écrire des lignes des codes pas du tout réutilisables mais bien respecter le pattern MVC plutôt que d'écrire du code qui sera peut-être un peu réutilisable mais qui s'écarte un peu du MVC. Tout ce que tu pourrais mettre dans une vue spécialisée, tu préfères le mettre dans le controller.

    Dans mon cas, la vue est réutilisable si on réutilise le modèle. Elle est juste spécialisée.


    Exemple concret : j'ai une map view qui affiche des lieux en cluster, j'ai mis l'algo de clustering dans le controller de la vue avec plein d'autres lignes de code pour gérer les boutons des barres d'outils, etc.

    Aujourd'hui, je voudrais faire un portage sur OSX, je regrette de ne pas avoir spécialiser MKMapView car je dois passer du temps à  extraire du code du controller pour pouvoir réutiliser cette fonction de clustering. Alors, oui, j'ai bien respecté le MVC mais j'ai obtenu un controller pas réutilisable et difficilement maintenable car bourré de code qui répond à  des delegates d'alertview, etc


    M'enfin bon si le MVC est la religion du moment pourquoi pas, chez les voisins, c'est le MVVM et c'est pareil, dès que quelqu'un a des problèmes, on lui dit que c'est de sa faute, qu'il a pas respecté le MVVM, et qu'il aurait pas dû essayer de réfléchir par lui-même.
  • Un exemple concret de ce que j'essaye de décrire : les annotations sous MapKit.


     


    Vous ajoutez une annotation, le contrôleur fait le lien entre l'annotation (model) et l'annovationView.


     


    Ensuite l'annotationView ne passe plus par le contrôleur pour afficher le nom et la description de l'annotation, elle a un pointeur vers l'objet du model (via un protocol).


    Si l'annotationView est draggable, elle peut même mettre à  jour les coordonnées de l'annotation directement sans passer par le contrôleur, quand l'utilisateur la déplace.


     


    Finalement, les bindings sont une généralisation de ce lien direct pour les cas où la conversion des propriétés est triviale (ou via un transformer).


    L'inconvénient de ce pattern, c'est le manque de contexte, il ne fonctionne que lorsque la vue n'a pas besoin de connaà®tre autre chose que son model associé. Si par exemple on veut afficher l'index d'un item dans uen collection (par exemple "item numéro x sur N"), alors on est gêné avec ce pattern.


  • @Joanna: Pyroh parle bien d'un ArrayController, puisqu'il mentionne le add, remove et undo. En plus, NSArrayController a un comportement particulier (je n'ai pas trouvé la doc, ça doit être un des ces trucs "magiques") quand il gère des Entities Core Data en lieu et place de NSMutableDictionaries.



    Il n'y a rien de magique là  dedans.


    Pour commencer NSMutableDictionnary est là  parce qu'il utilisé pour le NSObjectManager que sous-classe NSArrayController.


     


    Ensuite, le principe est simple: on prend une collection (NSMutableArray si on veut add/delete, NSArray sinon ou encore CoreData pour ceux qui aiment s'arracher les cheveux). Si on a un NS*Array il faut préciser la classe des objets contenus. D'expérience ça ne sert qu'à  l'autocomplétion pour les bindings. Si on a différentes classes on met NSObject histoire de faire propre (mais on va s'amuser pour répondre au type d'objet). 


    Le cas CoreData est plus simple on va fetcher dans la db (le NSManagedContext donc). Je ne sais pas si le nom de l'entity est forcément plus utile que le nom de la classe, j'utilise CoreData une fois toutes les morts de Pape.


     


    Maintenant que l'objet est loadé le contrôleur crée un objet proxy qui représente l'objet et expose ses bindings. Quand on fera des get/set le proxy va s'assurer qu'on peut, que la clé existe bien pour l'objet et surtout s'assurera de l'intégrité des données. L'avantage c'est la résistance aux erreurs: un mauvais accès aux données va résulter en une exception et non en un plantage pur et simple de l'application. En prime c'est plus facile à  débugger parce que les messages sont clairs.


     


    Bien entendu ça ajoute une couche d'abstraction mais suffisamment optimisée pour qu'elle soit négligeable en terme de performances. (on parle bien entendu du cadre d'une application type master detail qui manipule des data simplement, pas l'accès aux pixels d'une image à  analyser ou quoi que ce soit qui nécessite un accès rapide).


     


     


     


    Pour ce qui est des vues, il ne faut JAMAIS avoir la donnée à  représenter par pointeur sur le model, toujours copier sa valeur. Pourquoi ? Imagine ce que ferait un accès concurrent à  cette donnée précise.


     


    C'est pourquoi MKAnnotationView contient un contrôleur pour gérer le MKAnnotation que tu lui donne. Le protocol, ici n'est qu'une sorte de classe abstraite.


    J'en veux pour preuve que le protocol ne définit aucune méthode autres que get/set et la doc précise que les properties doivent être KVO compliant, attestant de l'utilisation d'un contrôleur intermédiaire. Contrôleur qui est sûrement un Observer pattern, qui peut être considéré comme contrôleur vu qu'on ne manipulera la donnée que dans un contexte précis.


     


    Dans tous les cas une vue ne doit pas manipuler directement une donnée et doit présenter une donnée copiée et valider l'entrée utilisateur avant de répercuter les changements.


  • berfisberfis Membre
    mars 2015 modifié #19

    Je suis agréablement surpris de l'ampleur prise par une question à  laquelle jpimbert a répondu en deux lignes! Mais il est vrai aussi qu'une fois la réponse lue, je me suis dit "OK, la méthode existe, maintenant elle s'imbrique où dans mon application?". Ce qui m'incite à  livrer quelques avis:


     


    • Le pattern MVC est pour moi un guide, une incitation à  penser et à  programmer proprement (éviter de coder comme un porc, comme je l'ai lu ici) mais pas forcément une religion. Pour avoir effleuré iOS, il me semble "mais je suis un noob complet" que sur OSX on sous-classe surtout les vues, et sous iOS surtout les contrôleurs (ça n'engage que moi). Les développeurs iOS avec qui je suis le cours n'avaient jamais entendu parler du MVC (mais ça n'engage qu'eux)... Le MVC existe sur ces deux plateformes proches, mais il est traité différemment. J'ai jeté un coup d'oeil sur MVVM, si la différence existe avec MVC, elle ne m'a pas sauté aux yeux (si, en fait, appeler le MVC Model - View - ViewModel c'est vraiment chercher la confusion linguistique)... Bref, si le MVC est une religion, c'est surtout les avis des ayatollahs qu'on entend...


     


    • Le MVC est d'autant plus utile que les bindings via IB ont un côté risqué: on peut binder dans le vide si la propriété n'est pas visible pour IB et il faut faire très attention au nommage des chemins. Un exemple trivial est celui des valeurs du NSUserDefaultController... Cette "face invisible" de l'application rend sa maintenance plus délicate.


     


    • Pour ce qui est d'un autre argument en faveur du MVC, qui revient souvent, c'est que la non-dépendance des classes permet leur réutilisation. En ce qui me concerne, je développe des applications (pour mon plaisir) qui sont des produits stand alone (en dehors des frameworks OSX). J'ai rarement pu réutiliser telle quelle une classe développée dans une autre application, sinon quelques NSViews ou NSArrayControllers personnalisés. En revanche, il m'est arrivé de réutiliser des méthodes, des parties de code ou des idées (géniales). Dans ce cas-là , c'est Copier/Coller/Editer (ou même pas parfois: je regarde comment j'ai fait et je reproduis les procédures).


     


    • Pour être adaptable, le MVC représente pour moi un énorme progrès " je vous rappelle que je viens d'un temps où on se débrouillait pour faire marcher des trucs qui s'écrivaient comme ça:


        213    IF (NB1REC.LE.0) GOTO 320


        214    GOTO 128


    Avec la programmation structurée, adieu les goto. Avec les langages orientés objets, adieu la relecture du code entier pour savoir ce qu'il advient de cette fichue variable globale qui n'a jamais la bonne valeur. Avec le MVC, adieu les classes fourre-tout ou les arborescences qui font dire "dommage que l'héritage multiple n'existe pas dans ce langage"... Alors si, de temps en temps, je me permets de croiser des pointeurs entre le Modèle et la Vue (en faisant gaffe au retain cycles) ça ne me semble pas risquer l'excommunication: cela nuit juste un peu à  mon sens esthétique.


     


    Pour résumer, mon hygiène de programmation tient en quelques mots:


    " il faut que ça marche: au bon moment, rapidement et que le résultat soit visible pour l'utilisateur (HIG).


    " il faut que ce soit bien écrit, de manière à  faciliter la maintenance (commentaires) et permettre l'ajout aisé de fonctionnalités.


    " il faut que ce soit factorisé: écrire deux fois le même code est un très mauvais signe.


    " il faut que ça reste raisonnable au niveau de la taille: dépasser le méga pour quelqu'un qui a économisé les K, c'est inconcevable. Je préfère sous-classer NSButtonCell pour bleuir l'icône sélectionnée que de coller douze images bleues dans IB. Les icônes monstrueuses réclamées par Apple pour l'AppStore prennent les 4/5 de la place du produit final. C'est débile, mais c'est dans l'air du temps: vendre du rêve bien emballé. Pour cela, merci le KVO, merci les bindings (@Draken: bien du plaisir avec Swift).


     


    En tout cas, bravo pour ce débat, il est rassurant: le développeur peut encore avoir son interprétation des patterns, sa liberté d'en user ou de les contourner, en un mot de posséder un style. Les développeurs ressemblent plus pour moi à  des artistes qui recherchent l'esthétisme qu'à  des pisseurs de code mécaniques et sans âme.


  • AliGatorAliGator Membre, Modérateur

    Exemple concret : j'ai une map view qui affiche des lieux en cluster, j'ai mis l'algo de clustering dans le controller de la vue avec plein d'autres lignes de code pour gérer les boutons des barres d'outils, etc.

    C'est une erreur. Le clustering est tellement un truc spécifique nécessitant une logique / un allo à  lui tout seul, tu aurais mieux fait de créer une classe dédiée (un ClusterManager ou je ne sais quoi) à  la gestion des données en clustering.

    Le MVC, comme tout Design Pattern, est un guide, ce n'est pas une vérité absolue. C'est le propre de l'architecture logicielle, il n'y a pas une solution unique toujours correcte, mais plusieurs bonnes solutions et façons d'aborder le sujet. Par contre les règles et bonnes pratiques de l'architecture logicielle disent aussi quelles sont les mauvaises pratiques.
    MVC ne dit pas qu'il faut tout mettre en vrac dans un seul gros Contrôleur. Le point important que dit MVC est que la Vue n'est pas sensée connaà®tre le Modèle et surtout vice-versa. On peut pour ça utiliser un seul contrôleur, ou plusieurs objets intermédiaires (dont le ViewModel de MVVM pourquoi pas), ou des Helpers / Managers, etc. Du moment qu'on ne mélange pas tout.

    D'ailleurs un des concepts importants justement derrière MVC c'est aussi et surtout d'isoler les responsabilités, de ne pas tout mélanger et faire un plat de spaghettis où tout est interdépendant, mais bien de faire des modules indépendants dont chacun a une responsabilité. Et en cela, parfois tout mettre dans un UIViewController qui fait tout, comme un gros bourrin, c'est justement pas une bonne solution (c'est pas parce que ça respecte le MVC que c'est propre, par contre si ça ne le respecte pas c'est sûr que ce n'est pas propre), il faut aussi rajouter des classes intermédiaires pour que chaque classe s'occupe uniquement de sa responsabilité (le clustering dans ton exemple)
  • Des nouvelles...


     


    J'ai rendu ma sous-classe de NSManagedObject un peu plus bavarde. Du coup elle claironne à  coup de NSNotifications tout ce qui lui arrive (creation, insertion, destruction, changement d'état). Un contrôleur de vue est à  l'écoute et répercute les changements du modèle vers sa vue. En lieu et place de pointeurs croisés, je passe par l'UUID de l'objet Core Data, dont une copie est stockée dans la subview. C'est leur seul lien.


     


    Autrement dit, l'objet se contrefiche de celui qui l'écoute et la subview se contente de se placer là  où on lui dit. Elle fait remonter ses mouseClicks vers le contrôleur qui met à  jour le modèle.


     


    ça marche (redo compris) et il me semble que c'est un peu plus MVC-compliant... non...?


  • AliGatorAliGator Membre, Modérateur

    Du coup elle claironne à  coup de NSNotifications tout ce qui lui arrive (creation, insertion, destruction, changement d'état). Un contrôleur de vue est à  l'écoute et répercute les changements du modèle vers sa vue.

    Heu pourquoi réinventer ce que fait déjà  NSManagedObjectContext, qui émet des tas de notifications à  chaque fois qu'un NSManagedObject est créé, inséré, détruit ou modifié dans ce MOC ?

    D'ailleurs c'est entièrement sur cela que se base NSFetchedResultsController (ce fameux contrôleur magique qui automatise les mise à  jour de l'UI en fonction des écoutes qu'il fait des changements du modèle) qu'on a l'habitude d'utiliser partout où on fait une UITableView/UICollectionView dont le but est d'afficher des objets CoreData et qui marche déjà  très bien... donc c'est dommage de réinventer la roue, non ?
  • Ali, je suis un spécialiste de réinvention de roue. Un de mes premiers programmes sur Mac dessinait ses propres boutons avec Quickdraw, jusqu'à  ce que quelqu'un (un peu comme toi) m'ait conseillé de lire Inside Macintosh... J'ai aussi bricolé durant des mois avec des NSMutableDictionaries avant de découvrir Core Data. Donc ça ne me dérange plus de réinventer l'eau tiède et je n'ai jamais regretté le temps passé à  le faire...


     


    NSFetchedResultsController, j'ai découvert son existence avec iOS "il me semble associé au UITableViewController travaillant avec Core Data. L'exemple en faisait même une propriété du UITableViewController.


     


    Quant à  l'émission de "plein de notifications", je pense que tu fais référence à  ceci:


    https://developer.apple.com/library/mac/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObjectContext_Class/#//apple_ref/doc/constant_group/NSManagedObjectContext_Change_Notification_User_Info_Keys


     


    ...où les changements sont annoncés à  l'échelle du contexte, avec les objets concernés passés dans un NSSet. Bah, le faire à  l'échelle de l'objet n'est pas vraiment réinventer la roue, non? Le contexte a un côté abstrait pour moi, j'aime bien les objets que je "chosifie". Ces notifications du contexte, c'est comme une boà®te de bonbons qui annoncerait "trois bonbons sont sortis et un a été remis à  l'intérieur" en lieu et place d'un bonbon qui dirait "je viens de sortir de la boà®te, mon emballage est jaune et je suis parfumé à  l'ananas"...


     


    Maintenant, si tu as une référence meilleure que la mienne, je suis preneur: la doc, c'est toi... -_-


  • AliGatorAliGator Membre, Modérateur
    Heu le CoreData Programming Guide au complet ça te va comme référence ? ;) Car oui les notifications ne te disent pas seulement le nombre de bonbons qui sont sortis mais elles te disent aussi lesquels et la couleur de leur papier et leur parfum. Tu peux même savoir ce qui a changé.


    Sauf que le gros avantage de la solution intégrée dans CoreData, outre le fait que ce soit justement intégré et que tu n'aies rien de + à  écrire pour que ça marche, c'est que tu n'as à  observer qu'un seul truc, le MOC, plutôt que d'observer chaque ManagedObject individuellement.


    Pour reprendre ton allégorie, c'est un petit peu comme si j'observais toute la boà®te d'un coup, alors que toi tu étais objet d'observer les bonbons un par un. Si il y a un bonbon qui est mangé, il faut que tu penses à  arrêter de l'observer. Si il y a un nouveau bonbon qui est ajouté il faut que tu penses à  l'observer. Alors que moi je continue à  observer ma boà®te et je vois tout passer, les bonbons mangés et ceux rajoutés, sans avoir à  faire quoi que ce soit de plus. Et en observant la boà®te cela ne m'empêche pas de savoir de quel bonbon il s'agit quand il y a des notifications.


    ---


    Réinventer la roue c'est bien mais juste pour le plaisir de découvrir comment ça marche sous le capot. Si c'est juste parce que tu es atteint du syndrome du NIH (Not Invented Here) et que tu refais les choses par principe d'avoir tout de fait maison genre tu n'as pas confiance en l'existant bah ça va te prendre du temps à  Réinventer des mécanismes non seulement existants mais surtout déjà  éprouvés et donc sur lesquels les bugs et cas d'erreur tricky auxquels tu ne vas pas penser de prime abord de ton côté (y compris des choses comme le thread-safety qui est loin d'être simple non seulement à  maà®triser mais aussi à  déboguer) seront déjà  résolus et consolidés avec la solution existante, mais en plus ces mécanismes ont été créés par des spécialistes en la matière bien souvent, donc à  moins que tu aies la prévention d'être un meilleur développeur que ceux de chez Apple tu vas sans doute faire moins bien et moins robuste que ce qui existe déjà ...
  • Non, pas de syndrome du NIH, en fait c'est juste dû à  ma connaissance superficielle de la doc (syndrome du GIACRAPYMI " give it a cursory reading and pretend you master it)  :D


     


    Bah moi aussi j'observe un seul objet, le [NSNotificationCenter defaultCenter]... et si tu reçois du MOC une NSManagedObjectContextObjectsDidChangeNotification, tu es bien obligé d'ouvrir les trois boà®tes du userInfo pour regarder les bonbons qu'elles contiennent, non?


  • AliGatorAliGator Membre, Modérateur
    Le NSNotificationCenter c'est le centralisateur des notifications. Ce n'est pas lui que tu observes. Vu falloir réviser !


    Le NC reçoit les notifications de tous ceux qui parlent et redistribue à  tous ceux qui écoutent, ou plutôt redistribue à  chaque observeurs ayant déclaré etre intéressé par tel objet toutes les notifications envoyées par ledit objet.


    Va aussi falloir relire les notions de base sur les notifications à  ce que je vois ;)




    Toi dans ton cas je suppose que tu passes nil comme objet quand tu demandes au NC de t'enregistrer comme observeur, histoire de dire que tu es intéressé par les notifications de tout le monde. Le problème c'est que ça peut vite faire du bruit avec beaucoup de notifications d'objets dont tu n'as cure... Et tout est en vrac.


    En gros tu observes tout le monde. Donc tous les bonbons. De toutes les boà®tes. Et aussi les papiers. Et les boà®tes. Et l'emballage. Et l'étiquette. Tout le monde.


    Alors que moi en utilisant le système intégré à  CoreData que je ne me suis pas embêté à  réinventer je n'observe qu'une seule boà®te pour observer les changements qu'il y a dedans. Et surtout s'il y a plusieurs boà®tes mais que je ne suis intéressé par les bonbons que d'une des boites (MOC) je peux n'observer que celle qui m'intéresse. Ce qui est plus souvent le cas que tu ne peux le penser avec tous les child MOC que je crée en général dans mon application selon les contextes, tantôt pour avoir un MOC dédié à  un ViewController, tantôt parce que j'ai besoin d'un MOC temporaire le temps du parsing d'un WebService (pour créer mes objets/bonbons en mode "bac à  sable" le temps du parsing et pouvoir facilement tout annuler si jamais je m'aperçois qu'il y a une erreur en plein pendant le parsing alors que j'ai déjà  commencé à  créer des objets et qu'il faut que j'annule tout la transaction, etc), et j'en passe.


    Si j'observais directement les bonbons je ne pourrais pas distinguer les objets créés dans une boà®te " celle qui m'intéresse, la vraie, le MOC qui sert pour remplir l'UI " de ceux créés dans une autre boà®te " un MOC temporaire pour le parsing ou autre " ce qui mélangerait tout et cas serait certainement la logique côté création de vues / UI qui risquerait de créer des vues pour des bonbons/objets qui ne sont pas liés à  l'UI...



    Mais bon je suppose que tu manques encore un peu de pratique pour réaliser tous les cas d'usage possible et les use cases auxquels on est habituellement confronté dans une application type utilisant CoreData ? Et c'est aussi pour cela que le Programming Guide est si important à  lire également...
  • berfisberfis Membre
    mars 2015 modifié #27

    Toutes mes recherches mènent à  ça en dernier ressort:



     


     


    Notifications

    A context posts notifications at various points"see NSManagedObjectContextObjectsDidChangeNotificationfor example. Typically, you should register to receive these notifications only from known contexts:



    [[NSNotificationCenter defaultCenter] addObserver:self


    • selector:@selector(<;#Selector name#>)
    • name:NSManagedObjectContextDidSaveNotification
    • object:<#A managed object context#>];


    Si ce n'est pas observer le centre de notifications, je me demande bien ce que c'est. Mais je vais réviser mon anglais tout de même...


     


    En fait je fais ça pour mon viewController:



    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(draftModification:) name:@DraftModification object:self.managedObjectContext];

    Donc je l'enregistre juste pour un type spécifique de notifications pour mon (unique) contexte, en somme je regarde les bonbons de la bonne boà®te. Je ne regarde pas trente-six boà®tes, ni les étiquettes, ni la marchande de bonbons...


     


    Observer le MOC, cela revient à  vérifier dans chaque clé (inserted, deleted, updated) s'il y a un ou plusieurs objets bonbons qui sont concernés, et à  laisser de côté les fourchettes, les Cadillac et les ratons-laveurs que le MOC m'envoie... Où est le gain?


  • Pour ce qui est des vues, il ne faut JAMAIS avoir la donnée à  représenter par pointeur sur le model, toujours copier sa valeur. Pourquoi ? Imagine ce que ferait un accès concurrent à  cette donnée précise.

     
    Je ne parle pas d'un pointeur sur une valeur. La valeur, par exemple la coordonnée sera quand même copiée.
    Si la valeur est un type (NSString*), le problème d'accès concurrent se pose de la même manière qu'avec un controller, je ne vois pas le rapport. A moins que tu gères la protection d'accès au niveau du contrôleur ?
     
     

    C'est pourquoi MKAnnotationView contient un contrôleur pour gérer le MKAnnotation que tu lui donne. Le protocol, ici n'est qu'une sorte de classe abstraite.

    Le fait d'utiliser un protocol ne règle pas le problème d'accès concurrent.

    J'en veux pour preuve que le protocol ne définit aucune méthode autres que get/set et la doc précise que les properties doivent être KVO compliant, attestant de l'utilisation d'un contrôleur intermédiaire. Contrôleur qui est sûrement un Observer pattern, qui peut être considéré comme contrôleur vu qu'on ne manipulera la donnée que dans un contexte précis.

    Spéculations...
    Mais intéressantes car tu introduis le fait qu'un 'controller' soit présent DANS la "vue", on y reviendra.

    Dans tous les cas une vue ne doit pas manipuler directement une donnée et doit présenter une donnée copiée et valider l'entrée utilisateur avant de répercuter les changements.

    La validation des données est en effet une bonne raison d'avoir un controller intémerdiaire car souvent elle requiert une connaissance du contexte.
    Par exemple, si il y a des zones où on n'a pas le droit de déplacer une annotation, alors il va falloir qu'un controller qui a une vue sur la carte entière sache que l'annotation a été déplacée dans une zone interdite. Surtout si la vérification est non triviale, genre, il faut vérifier qu'il y a déjà  une annotation dans cette zone, donc on ne peut pas en mettre une deuxième.
  • AliGatorAliGator Membre, Modérateur
    mars 2015 modifié #29


    Si ce n'est pas observer le centre de notifications, je me demande bien ce que c'est.

    Heu non ce n'est pas du tout observer le centre de notifications (cette phrase n'a même pas de sens)


    C'est plutôt observer le ManagedObjectContext là  ce que tu fais avec ce code (ce qui revient du coup à  avoir encore + réinventé la roue que je ne pensais...) puisque c'est le MOC que tu passes au paramètre "object" indiquant l'objet à  observer... à  se demander si tu as lu la doc de cette méthode...


    Plus exactement ce code demande au Notification enter d'enregistrer "self" (tin ViewController certainement) en tant qu'observeur de l'objet ManagedObjectContext pour les notifications ayant le nom indiqué (en tant qu'observeur des notifications DraftModification émises par l'objet indiqué, ici le MOC).


    Le NC ne fait que faire passe plat. Ce n'est pas lui qui est observé. Observer le NC n'a pas de sens. C'est comme si tu disais que tu démarrais ta clé de voiture ou que tu ouvrais ta clé de maison.
  • FKDEVFKDEV Membre
    mars 2015 modifié #30

    C'est une erreur. Le clustering est tellement un truc spécifique nécessitant une logique / un allo à  lui tout seul, tu aurais mieux fait de créer une classe dédiée (un ClusterManager ou je ne sais quoi) à  la gestion des données en clustering.



    Tu m'étonnes...

     



    MVC ne dit pas qu'il faut tout mettre en vrac dans un seul gros Contrôleur. Le point important que dit MVC est que la Vue n'est pas sensée connaà®tre le Modèle et surtout vice-versa. On peut pour ça utiliser un seul contrôleur, ou plusieurs objets intermédiaires (dont le ViewModel de MVVM pourquoi pas), ou des Helpers / Managers, etc. Du moment qu'on ne mélange pas tout.




    Il y a deux problèmes avec le MVC sous iOS/Cocoa :


    1/ Le premier c'est le singulier. Pourquoi aurait-on un seul controller pour gérer un écran qui est construit à  partir de plusieurs vues et qui affiche plusieurs entitées du modèle ?

    Les vues et le modèle sont au pluriel mais le controller est au singulier.

    L'hitstorique et les tuto sur iOS tendent à  propager ce modèle qui date de l'époque où un écran iOS était juste une table view.

    Donc il ne faut pas considérer LE controller en tant qu'objet unique mais en tant que rôle que l'on a le droit de répartir sur plusieurs objets.


    2/ Le fait que chacun interprête différement les rôles du controller et de la vue.

    Apple indique que la vue ne doit pas connaà®tre le modèle (ici).

    Alors que pour d'autres un modèle en triangle où la vue connait le modèle en lecture seule est acceptable, cf ici :
    http://fr.wikipedia.org/wiki/Mod%C3%A8le-vue-contr%C3%B4leur

     


    Là  où tout le monde est d'accord :


    -le modèle ne connait personne,


    -la vue n'agit pas directement pour modifier le modèle, (ce qui est enfreint par MKAnnotationView, par berfis et parfois par moi-même.  Ce qui ne signifie pas que c'est blasphème mais que c'est dangereux et qu'il faut être prudent si on le fait).


     


    La raison que donne Apple pour le découplage est :


     


    Because you typically reuse and reconfigure them, view objects provide consistency between applications. Both the UIKit and AppKit frameworks provide collections of view classes, and Interface Builder offers dozens of view objects in its Library.

     



     


    Donc on parle bien de View objects réutilisables, ceux fournis par Apple ou par des développeurs talentueux, pas des vues spécialisées que vous écrivez en 10 minutes. J'en viens à  mon troisième point.


     


    3/ Sous iOS, la croyance que toute classe qui dérive de XXXXXView (souvent de UITableViewCell) doit être une vue réutilisable au sens de MVC.

    Ecrire une vue réutilisable n'est pas un travail de tous les jours.

    Une vue réutilisable c'est OHAttributedLabel ou SMCalloutView.



    Admettons que je veuille faire une UITableViewCell pour afficher une track d'une playlist avec une image à  gauche (l'album) et une image à  droite (l'artiste).


    Est-ce que je dois faire/chercher une vue réutilisable avec deux imageView ou claquer une nouvelle vue en 10 minutes dans IB ?

    (AXIOME : une classe est réutilisable si cela va plus vite de la trouver, de la comprendre et de la réutiliser que de la refaire).

    D'autant qu'utiliser une vue réutilisable a un coût en terme d'abstraction et de sémantique :

    dans une vue non réutilisable les images s'appelleront leftImageView et rightImageView, dans une vue spécialisée, artisteImageView et albumImageView.



    Il faut voir que ma classe FKDTrackViewCell qui sert à  afficher une track n'est pas vraiment une vue, elle n'a pas de méthode DrawRect et grâce à  IB, elle n'a peut-être même pas de méthode layoutSubView. Je dois nécessairement la créer pour avoir les IBOulet artisteImageView et albumImageView, mais ce n'est pas vraiment une vue au sens où Apple l'entend ci-dessus.


    Si je lui mets un pointeur vers un objet Track (ou id<Track>), j'en fait un mini-controller qui va permettre de soulager le controller principal et aussi de mieux répartir mon code.

    Bien sûr cette vue n'est pas réutilisable au sens où je ne vais pas la mettre sur github. Par contre, je peux facilement la réutiliser dans différents écrans du même projet. Ou faire un portage sous OSX sans avoir à  décortiquer le controller. Finalement le fait d'avoir bien séparer les responsabilités va créer du potentiel.


    Attention, je ne dis pas qu'il faut faire des vues/controller à  tous les coups, je dis juste qu'il ne faut pas se l'interdire sous prétexte que, comme c'est une 'XXXXXXXView' qui a un ancêtre de type UIView, elle ne peut pas assumer une partie du rôle de controller.


     


    Quand j'ai démarré le métier, je ne connaissais pas MVC, j'ai fait des erreurs et j'ai vu des horreurs.

    Et j'ai appris durement un principe basique qui est : ne jamais mélanger les données et le GUI. MVC est un pattern plus élaboré qui découle de ce principe.

    Par exemple, j'ai vu un projet où l'état actif/inactif d'une famille d'objets non persistent n'existait que dans la case à  cocher d'un formulaire. Quand j'ai voulu faire des traitement en batch sur ces objets, ça n'a pas été possible. 



    Pour être un bon développeur, il ne faut pas respecter tel ou tel pattern, il faut avoir l'intuition de ce qu'est un code pas propre. Si on passe son temps à  respecter bêtement des patterns sans réfléchir, ni expérimenter, on acquiert jamais cette intuition.

    Quelqu'un va arriver avec un controller qui fait 10000 lignes, vous allez lui dire que c'est de la merde et il va répondre que non car il a respecté le pattern MVC, et que d'ailleurs ses vues et son modèle sont réutilisables. OK mais elles ne servent à  rien car tout le code est dans le controller de 10k lignes.


     


    Donc, quand on dit à  un développeur, tu n'as pas respecté MVC, donc c'est forcément de la merde, ce n'est pas toujours vrai. Et l'inverse n'est pas non plus un gage de qualité.


    Sans connaà®tre MVC, on peut très bien avoir l'intuition d'un code propre et bien découpé.

  • AliGatorAliGator Membre, Modérateur
    mars 2015 modifié #31
    +1 FKDEV tu as tout dit.

    Comme j'ai déjà  mentionné plus haut, un Design Pattern n'est qu'un guide. Le respecter au pied de la lettre juste pour le principe de le respecter n'a pas de sens. Il n'y a pas une seule bonne architecture, une seule bonne solution, il faut savoir choisir le bon Design Pattern ou la bonne architecture en fonction du besoins. Par contre il y a des mauvaises pratiques (comme mélanger le code de la Vue et du Modèle, comme tu donnes en exemple avec ta case à  cocher), et respecter les patterns comme MVC permet de s'assurer qu'on ne fais pas ce genre d'erreur.

    Je rajouterais juste que de plus en plus sur iOS il n'y a pas un seul mais plusieurs UIViewControllers, donc plusieurs Controllers, avec des childViewControllers. Et c'est une bonne chose, cela permet d'isoler la responsabilité de chacun.

    Comme tu le soulignes, le but n'est pas tant d'être "réutilisable", mais d'être découplé du reste de l'application. Pour que si on change un élément côté Modèle, la Vue n'aie pas à  être impacté. Pour que l'application ne soit pas un gros plat de spaghettis, en somme, mais que chacun s'occupe de ses oignons. (mince, ça me donne faim tout ça :D) Et que si un truc change côté vue ça ne t'oblige pas à  changer aussi ton modèle, et vice-versa.

    Si ta vue dépend de ton modèle (ou l'inverse) ce n'est pas "mal" *juste* parce que ça ne respecte pas le MVC, mais pour des raisons + vastes. Souvent dans ce cas on reproche juste "tu n'as pas respecté le MVC, c'est mal" mais c'est un peu un raccourci, ce qu'on veut dire en fait c'est surtout "tu n'as pas respecté un bon découpage qui sépare ta vue de ton modèle, les deux ne sont pas indépendants, et cela présage bien souvent d'un problème de conception plus profond, il faut donc revoir ton architecture. Et respecter le MVC est un des moyens / guide qui t'aidera à  aller dans ce sens".
Connectez-vous ou Inscrivez-vous pour répondre.