De l'utilisation des protocols avec setter

avril 2011 modifié dans API AppKit #1
Bonjour à  tous,

Encore moi et mes questions à  deux francs (je sais, ça n'existe plus).
Je suis très familier avec les protocols, je créé énormément de controls persos de type MVC avec un protocol et tout le tintouin.
Récemment, j'ai aperçu un protocol qui requiert "setMachinChose:"... Et c'est là  que je trouve la chose assez bizarre.
Autant, je conçois totalement les protocols en terme de 'getters', c'est à  dire que ma classe Toto doit se conformer à  mon protocol Machin, qui lui requiert des getters comme 'imageVersion', 'imageTitle', etc..
L'exemple le plus concret pour ce genre d'utilisation est IKImageBrowserItem protocol.
IKImageBrowserItem, en soit, n'existe pas. Il faut créer une sous-classe de NSObject et implémenter le protocol qu'il faut afin de répondre aux attentes de la vue, qui requiert entre autre "imageTitle' et cie.
Chose étonnante, que je n'ai jamais vu chez Apple mais sur un projet d'un des développeurs de ma boà®te, c'est l'ajout d'un proto 'setMachinChose:' dans le protocol. En gros, chaque objets qui se conforment au protocol "Machin" doivent implémenter "setMachinChose:".

exemple:

<br /><br />@protocol MyProtocol<br />- (void)setLinkedViewController:(UIViewController*)aLinkedViewController;<br />@end<br /><br />.....<br /><br />@interface MyViewController : UIViewController &lt;MyProtocol&gt;<br />{<br /><br />}<br />@property (nonatomic, retain) UIViewController *linkedViewController;<br />@end<br /><br />@implementation MyViewController<br />@synthesize linkedViewController;<br />- (void)setLinkedViewController:(UIViewController*)aLinkedViewController<br />{<br />	........ // rewrite the setter..<br />}<br /><br />- (void)dealloc<br />{<br />	[super dealloc];<br />	self.linkedViewController=nil;<br />}<br />


En gros, cela lui aura permis, d'éviter de créer une sous-classe de UIViewController, étant donné que chacun de ses UIViewController dans son projet devra implémenter setLinkedViewController: et donc avoir une iVar associé à  ça.

Non pas que je ne comprenne pas le principe, car effectivement si le UIViewController se conforme à  ce protocol, on aura vite un warning si on oublie de créer la iVar 'linkedViewController', mais je trouve ça assez étrange comme façon de faire et j'aurai aimé avoir la confirmation d'autres personnes sur cette manière de procéder avec les protocol.

Personnellement, j'aurai joué la sous-classe... Mais d'un autre côté il est vrai que si le développeur veut aussi que ça fonctionne sur une UITableViewController, c'est assez pratique..
C'est juste qu'en soit, je trouve ça bizarre et que je n'ai jamais vu ça dans les protocol Apple pour l'instant (ou bien ça a pas dû m'interpeler

Réponses

  • CéroceCéroce Membre, Modérateur
    08:33 modifié #2
    Dans beaucoup de langages objet, les protocoles n'existent pas. Ce qui existe, c'est l'héritage multiple: un objet peut hériter de plusieurs objets à  la fois. De fait, l'équivalent d'un protocole est une classe abstraite pure: elle définit les interfaces mais ne propose pas d'implémentation.

    Dans Objective-C et Java, on ne dispose que de l'héritage simple: on ne peut hériter que d'un objet à  la fois. En pratique, c'est manière à  plus de sens:
    - la classe définit la nature de l'objet
    - le protocole définit les personnalisations possible du comportement.

    Ce qui ramène à  ta question: dans quelque cas un protocole doit-il obliger à  implémenter une propriété ? À partir du moment où cette propriété sert à  définir le comportement.

    (La réponse n'est pas binaire, le choix fait appel au goût personnel et l'expérience).
  • 08:33 modifié #3
    D'accord, mais lorsqu'on travaille à  plusieurs, il y a forcément des règles à  suivre... Donc ma question est bien de savoir si cette pratique est monnaie courante (ce que je doute) ou ne préférerait-on pas utiliser une sous-classe.. ?
  • AliGatorAliGator Membre, Modérateur
    08:33 modifié #4
    Moi ça me choque ce setter dans le protocol.

    Ca rajoute un lien fort entre le protocole et la classe qui s'y conforme, ça t'oblige à  ce que ta classe implémente un setter alors que celui qui va envoyer les messages du @protocol à  ta classe lui n'a rien à  voir avec ce setter, si ? Et en tout cas ça oblige surtout ta classe à  avoir une @property linkedViewController, donc ça oblige ta classe à  avoir une certaine structure dans ses variables d'instances et ses propriétés pour que le tout fonctionne, et ça ça viole le principe de séparation classe/protocole.

    A la limite, si l'objet qui émet des messages au delegate, l'objet qui appelle les méthodes du @protocol à  destination de ta classe donc, doit demander à  ce que ta classe mette à  jour le linkedViewController, pourquoi pas une méthode du @protocol "myProtocol:changeLinkedViewControllerTo:", que chacun aurait à  implémenter comme il le veux, typiquement bien souvent en appelant le setter de linkedViewController finalement, mais au moins si tes classes ont décidé d'appeler ta propriété autrement ça marche aussi). Mais je ne suis pas sûr que ce soit la question ici.

    - Si cette méthode est déclarée dans le @protocol uniquement pour être sûr que tu penses à  implémenter cette méthode, alors qu'elle n'a rien à  voir avec le @protocol et par exemple la notion de delegate et de "messages informatifs" que le @protocol véhicule, ce setter n'a pas sa place dans le @protocol
    Un protocol en général, surtout dans les cas des delegate et datasources, c'est utilisé pour déclarer des méthodes "questionnantes" (dataSource) genre "quel est le nombre d'éléménts à  afficher", "donne moi le nom de l'élément X", ou "informatives" (delegate) genre  "attention je viens de sélectionner l'élément X). Rien à  voir avec les setters

    - De toute façon cette méthode ne va en rien t'obliger à  implémenter le setter, car si tu implémentes un setter sans getter juste comme ça, ou si tu fais un @synthesize mais n'écris nulle part explicitement le setter, ça passera (alors que ça ne devrait pas si j'ai bien compris le but de cette utilisation détournée du @protocol)
  • CéroceCéroce Membre, Modérateur
    08:33 modifié #5
    Je vous avoue que la possibilité de déclarer des propriétés dans un protocole m'avait fort surpris à  l'époque.
    ça me semble également contraire à  l'esprit de "code par rapport à  l'interface, pas pas rapport à  l'implémentation".
  • AliGatorAliGator Membre, Modérateur
    08:33 modifié #6
    En fait déclarer une @property dans un @protocol peut avoir du sens, si on utilise le @protocol pour autre chose que delegate ou datasource, mais plutôt comme un template de qualification de classe.

    Je pense par exemple à  un @protocol définissant des commandes ou des interfaces communes, par exemple disons un @protocol Playable :
    @protocol PlayableItem<br />-(void)play;<br />-(void)pause;<br />-(void)stop;<br />// la ligne suivante revient à  déclarer -(NSTimeInterval)position et -(void)setPosition:(NSTimeInterval)v<br />@property(nonatomic,assign) NSTimeInterval position;<br />@property(nonatomic,readonly) NSTimeInterval duration; // revient à  déclarer -(NSTimeInterval)duration;<br />@end
    
    On pourrait alors avoir une classe VideoPlayerView, qui dérive de UIView, et qui se conforme au protocole <Playable>. Une classe AudioPlayerView qui fait pareil. Une classe AudioFile, qui dérive de NSFileHandle par exemple ou autre, et également conforme au protocole <Playable>, un appel à  "play" jouant le son dans les haut-parleurs, etc...

    Dans ce genre de cas d'usage, déclarer des @property dans le @protocol me semble tout à  fait correct et justifié. Mais les @property ne sont alors pas liées à  des variables d'instance de l'objet, puisqu'un @protocole ne définit pas une classe mais juste une "API" que va respecter une classe donc juste des méthodes.
    Libre ensuite à  chaque classe d'implémenter les méthodes -setPosition et -position comme il faut, par exemple dans une classe VideoFile cela pourra modifier une variable currentFrameIndex (genre [tt]-(void)setPosition:(NSTimeInterval)pos { currentFrameIndex = framesPerSecond * pos; }[/tt]) ou se baser sur cette variable pour retourner la position (genre [tt]-(NSTimeInterval)position { return  currentFrameIndex/(float)framesPerSecond; }[/tt]), alors que pour une AudioFile ça se basera sur le samplingRate (nbSamplesPerSecond)...
    Pour une autre classe qui jouerai je ne sais pas par exemple un scénario (genre fichier SMIL), ça ferait encore autre chose pour se positionner à  la position demandée, etc.

    Dans ce genre de cas d'usage, déclarer des @property dans des @protocol c'est plutôt dans le sens API, pour déclarer qu'on peut demander à  un objet la position ou lui demander de changer cette position. Mais dans le @protocol il n'y a pas l'ivar associée, un protocol n'était qu'une liste de méthodes. Libre à  celui qui implémente une classe se conformant à  se protocol d'implémenter la méthode comme il le veut, via une ivar ou pas.


    Mais pour moi ce sont deux cas d'usage distincts des @protocol, un peu comme on distingue souvent les @protocol servant de "delegate" de ceux servant de "dataSource", l'un étant plutôt pour faire du signalement d'événement, l'autre étant plus pour demander à  un objet qu'il nous fournisse des données, et ben dans le 3e cas que j'ai évoqué ici avec le protocole Playable, c'est plutôt une liste d'actions possibles sur l'objet que l'on doit implémenter pour se conformer au protocole.
    Le framework Cocoa utilise d'ailleurs parfois les @protocol dans ce sens, par exemple pour le protocole CAAction ou CAMediaTiming, ou même à  la limite le protocole NSCoding

    Mais par contre mixer ces 3 usages (delegate, datasource, actions) dans un protocol unique par contre là  je trouve que c'est pas très propre et c'est mélanger les concepts
  • 08:33 modifié #7
    Merci pour le topo  ;)
  • JegnuXJegnuX Membre
    08:33 modifié #8
    Salut à  tous !

    Je suis le collègue dont louka parle, donc je viens ici un peu expliquer ma démarche.

    En gros, j'ai créé une sous classe de UIViewController qui est une sorte de méga SplitView controller (Non plus 2 zones, mais 5 zones possibles, oui c'est moche, mais je suis obligé :x)

    l'idée c'est que tout les view controllers que je mettrais dans ma méga splitview soient au courant qu'ils y sont dedans. Alors je voulais utiliser la property parentViewController UIViewController mais comme vous le savez, c'est du readonly... Donc en plus des property parentViewController, splitviewController, navigationController, tabBarController etc... je voulais ajouter une property megaSplitViewController.

    Et du coup à  chaque fois que j'ajoute un view controller dans mon mega splitview, je fais un setMegaSplitViewController:self et le tour est joué.

    L'avantage ici, c'est que dans tout les cas, tout ce que je mettrais dans mon mega SplitView, sera une sous-classe perso, mais ça peut être une sous-classe de UINavigationController ou encore de UITableViewController. Donc, comme souligné plus haut, comme l'héritage multiple n'existe pas en Obj-C, j'ai pensé aux protocoles pour forcer mes sous classé à  implémenter une property megaSplitViewController.

    Comme je ne suis pas seul sur le projet (il y a notamment deux stagiaires), c'était pour moi une façon de rappeller via un warning de bien penser à  ajouter la property (sans même forcément implémenter à  la main, un @synthesize passe bien aussi).

    Mais si vous avez une façon plus élégante de faire ça, je suis preneur :)
  • AliGatorAliGator Membre, Modérateur
    08:33 modifié #9
    Alors dans ce cas moi je nommerais plutôt la méthode en question [tt]-(void)didAddToMegaSplitViewController:(MegaSplitViewController*)msvc;[/tt] ça me parait plus cohérent.

    Ainsi quand tu ajoutes un ViewController à  ton MegaSplitViewController, ce dernier au lieu d'appeler [tt][vc setMegaSplitViewController:self][/tt] sur le ViewController vc que tu viens d'ajouter, il va appeler [tt][vc didAddToMegaSplitViewController:self][/tt]

    Le fonctionnement est exactement pareil, mais c'est le nommage qui change tout, tu es bien plus conforme ainsi aux conventions de nommage de Cocoa et de l'idée du @protocol utilisé en tant que delegate : le MSVC va informer le contrôlleur ajouté qu'il a changé de MSVC parent.

    Et dans le code de tes ViewControllers implémentant le delegate (les VC que tu vas ajouter à  ta MSVC) ? Et bien ça ne change pas trop non plus, chaque ViewController va implémenter un truc du genre :
    -(void)didAddToMegaSplitViewController:(MegaSplitViewController*)msvc {<br />&nbsp; // par exemple, si le ViewController concerné veut stocker dans sa @property megaSplitVC<br />&nbsp; // " qui lui est propre et qu&#039;il a déclarée dans son coin " une référence vers le megaSplitVC parent<br />&nbsp; self.megaSplitVC = msvc;<br />}
    
    Et de même idéalement soit tu prévois une méthode symétrique [tt]-(void)didRemoveFromMegaSplitViewController:(MegaSplitViewController*)msvc[/tt] pour permettre au VC qui se conforme à  ton protocole de remettre sa référence vers ton MSVC à  nil, soit tu utilises la même méthode [tt][vc didAddToMegaSplitViewController:nil][/tt]. Quitte à  du coup plutôt la nommer "[tt]didChangeParentMegaSplitViewController:[/tt]" du coup plutôt dans ce cas pour mieux illustrer son usage.


    Conclusion au final ça revient à  peu près au même que ta solution, mais :
    - Rien que le changement de nom te fait mieux adhérer aux principes du pattern delegate de ton @protocol
    - Avec cette modification mineure en plus cela découple bien ton MegaSplitViewController des VC qui vont implémenter ton @protocol, puisque tu ne leur impose rien au niveau de leur construction interne (variables d'instance à  utiliser, etc) : quand les ViewControllers vont recevoir ce message de ton @protocol, ils feront ce qu'ils veulent de ce message / cet "événement" leur informant que leur MSVC parent a changé. Ils s'en serviront ou pas, à  eux de voir. Tu restes ainsi dans le domaine du "signalement d'informations" propre à  l'usage des "delegate" classique en Cocoa, tu signales le changement de MSVC mais n'impose aucune action.
Connectez-vous ou Inscrivez-vous pour répondre.