[Débutant swift] .h versus protocol

Hello à  tous !


 


Je me mets à  swift et j'avoue que la perte des .h me rend chagrin. Je trouvais ça sympa d'avoir une interface à  mes classes.


 


Quel est votre avis ? Est-ce que adjoindre un protocol à  toute les classes serait une bonne option ? Quelles sont vos pratiques ?


 


Merci !


Réponses

  • AliGatorAliGator Membre, Modérateur

    Quelles sont vos pratiques ?

    Sur le principe, tu peux demander à  Xcode (7.0) de générer les interfaces (équivalent des .h ObjC) pour toi. Donc tu as toujours la possibilité de voir les interfaces publiques exposées par ton code assez facilement.
    (Normalement il y a aussi le menu "Navigate" > "Jump to Generated Interface" " auquel tu pourras associer un raccourci clavier si tu l'utilises souvent " mais passer par là  a l'air de moins bien marcher dans la 7.0b5 que passer par le petit menu en haut à  gauche. Ca devrait être réglé avec la 7.0 officielle je pense)
  • Mon opinion est simple : Mort aux .h !  J'ai toujours eu ça en horreur.

  • AliGatorAliGator Membre, Modérateur
    Et sinon pour répondre à  ta question de façon un peu plus complète, ça n'empêche pas quand même de faire d'avantage de protocols. En effet Swift est un langage bien + orienté protocoles (Protocol-Oriented Programming) qu'orienté objet, dans le sens où vu comment la stdlib de Swift est construite et comment le langage nous incite à  aller dans cette direction, tu as tout intérêt à  user des protocoles en Swift.

    Pas tant pour avoir une sorte d'interface qui ferait office de remplaçant au ".h", mais plutôt parce que ça t'apporte + de flexibilité et d'abstraction d'utiliser des protocoles (tu n'imposes pas une classe en particulier, mais juste un contrat d'interface disant je sais répondre à  telle et telle fonction), du coup ça n'impose pas une hiérarchie de classe au reste du code.
    Je viens justement de tomber sur un article de blog qui explique ça ici, mais c'est loin d'être le seul. J'avais aussi cité dans un autre post les talks de Airspeed Velocity dont un qui explique les avantages d'utiliser des protocoles pour faire du typage faible plutôt que du typage fort.

    Et en pratique, avec Swift 2.0 qui a apporté la possibilité pour les protocoles de fournir une implémentation par défaut pour leurs méthodes via des extensions, utiliser des protocoles devient très puissant et peut te permettre de faire des choses qu'avant tu ne pouvais faire qu'en sous-classant.
    Par exemple si tu veux que 80% de tes UIViewControllers aient une méthode "presentError:" pour afficher tout seuls une erreur à  l'écran, tu pourrais créer une sous-classe ErrorPresentingViewController qui dérive de UIViewController, et ensuite faire que tous tes VC de ton appli héritent de ErrorPresentingViewController plutôt que d'hériter directement de UIViewController. Oui, mais quid de si tu veux un UITableViewController ? Du coup tu ne peux plus hériter de ErrorPresentingViewController ! Donc tu es obligé soit de créer une autre sous-classe ErrorPresentingTableViewController qui a le même code que ErrorPresentingViewController mais hérite quant à  lui de UITableViewController, ce qui est assez moche car fait de la duplication de code... Ou bien créer une extension (catégorie) sur UIViewController mais dans ce cas tout le monde l'aura et pas que les 80% de VC intéressés... Bref y'a pas de belle solution (la mieux étant d'essayer d'utiliser la composition plutôt que l'héritage, mais bon ça reste pas aussi sympa à  utiliser du coup)

    En Swift, tu vas plutôt préférer faire un protocole ErrorPresenter, qui fournit une implémentation par défaut de sa méthode presentError(). Et tu pourras créer autant de classes héritant, au choix, de UIViewController ou UITableViewController ou même UICollectionViewController ou même n'importe quoi, et rien qu'en indiquant qu'elle se conforme à  ErrorPresenter tu auras automatiquement la méthode presentError() avec son implémentation par défaut. Du coup en se conformant à  un protocole, tu adoptes automatiquement de nouvelles fonctionnalités. Avec l'héritage tu ne pourrais pas faire ça sans imposer d'hériter d'une classe particulière, et du coup ça ne t'arrange pas toujours (et en + ça marche pour une fonctionnalité mais ne permet pas d'hériter de plusieurs classes d'un coup alors qu'avec les protocoles on peut se conformer à  plusieurs)

    Du coup même si c'est pas dans l'unique but de retrouver l'équivalent d'un ".h", c'est quand même une bonne idée d'essayer d'utiliser au maximum les protocoles en Swift, le langage étant même pas mal pensé dans ce sens pour t'y inciter aussi, et ça apporte pas mal d'avantages et surtout de flexibilité.
  • AliGatorAliGator Membre, Modérateur

    Mon opinion est simple : Mort aux .h !  J'ai toujours eu ça en horreur.

    Ce qui est chiant des ".h" c'est pas tant leur utilité. Car c'est quand même appréciable d'avoir la possibilité quand je lis une classe que je veux utiliser de n'avoir, d'un coup d'oeil, QUE l'interface publique, sans être pollué par l'implémentation dont " si la classe est bien faite " je me fous éperdument et ne devrait pas avoir à  me soucier, et sans être pollué non plus par les fonctions et variables privées.

    Non, ce qui est chiant des ".h", c'est de les écrire, et qu'il faut les maintenir. Si tu rajoutes une méthode publique dans ton ".m" il faut systématiquement penser à  aller la rajouter dans ton ".h", et pareil si tu la modifies plus tard il faut penser à  reporter. C'est ça qui est lourdingue.

    Avec Swift, plus de problème : c'est Xcode qui te génère automatiquement l'interface publique pour toi (cf ma capture du post précédent), comme ça si tu veux juste connaà®tre l'API d'une classe que tu connais pas tu peux savoir quelles sont les méthodes que tu as le droit d'appeler. Pas de maintenance, rien à  écrire de ta part, pas besoin de copier/coller les signatures de méthodes à  chaque fois... mais ça permet quand même de savoir l'API d'une classe inconnue (ou de vérifier en un clin d'oeil, si tu es l'auteur de ladite classe, que tu n'as bien exposé QUE ce que tu voulais et rien de +)
  • DrakenDraken Membre
    août 2015 modifié #6


    Non, ce qui est chiant des ".h", c'est de les écrire, et qu'il faut les maintenir. Si tu rajoutes une méthode publique dans ton ".m" il faut systématiquement penser à  aller la rajouter dans ton ".h", et pareil si tu la modifies plus tard il faut penser à  reporter. C'est ça qui est lourdingue.


     




    Oui, c'est exactement ça qui m'a toujours agacé avec les .h. Tant mieux si Xcode 7 le fait automatiquement pour Swift.


  • Merci de vos réponses.


     




    Non, ce qui est chiant des ".h", c'est de les écrire, et qu'il faut les maintenir. 




     


    Moi j'aime bien les écrire. J'aime bien le moment où je crée ma classe et où je commence à  écrire le .h avant d'écrire le .m. C'est une démarche assez protocolaire et du coup je me demandais si l'on pouvait imaginer une pratique du type à  chaque classe son protocol. Je pense que aller aussi loin serait inutile mais il y a peut-être quelque chose à  creuser, utiliser plus les protocoles. Et j'aime moins les maintenir.


     


    Du coup, il faudrait imaginer une convention de nommage du type : Je veux une classe Car. Je crée d'abord un protocole Car et ensuite la classe MyCar ou CarImplementation. C'est un peu lourd néanmoins.


     


    Concernant la doc, ne manquez pas la vidéo WWDC sympa : https://developer.apple.com/videos/wwdc/2015/?id=408


     


    Questions :


    - que se passe-t-il si j'override la méthode de l'extension ? Y a-t-il un problème ? J'ai le souvenir d'un passage dans la vidéo où ce n'est pas la même méthode qui est appelée selon le type déclarer de l'instance (ouf, non ?)


    - comment indiquer qu'une méthode ne doit pas apparaà®tre dans le pseudo .h ?


     


     


    Sinon, c'est clair que les extensions de protocol c'est trop génial !!!


  • AliGatorAliGator Membre, Modérateur

    Moi j'aime bien les écrire. J'aime bien le moment où je crée ma classe et où je commence à  écrire le .h avant d'écrire le .m. C'est une démarche assez protocolaire et du coup je me demandais si l'on pouvait imaginer une pratique du type à  chaque classe son protocol.

    Effectivement, c'est une bonne démarche de commencer par l'API avant de partir bille en tête sur l'implémentation.

    Et justement comme le dit la citation mise en avant dans l'article plus haut, à  l'extrême Apple préconise en Swift de démarrer avec un protocole avant de voir si tu as vraiment besoin d'une classe. Bon en pratique tu vas pas le faire à  chaque fois et à  force tu vas savoir d'avance si un protocole peut te suffire ou si une classe est mieux, mais c'est une stratégie intéressant quand même, ça permet de se forcer à  faire un couplage faible tant que faire se peut entre tes objets, en utilisant des protocoles et non des types concrets pour tes propriétés servant à  lier tes objets.

    Par exemple si tu as une classe Person, ça peut être intéressant plutôt que sa propriété "animals" soit de type "[Animal]", qu'elle soit de type [AnimalType], pour ne pas imposer que tous les animaux que tu vas mettre dans ce tableau dérivent forcément d'une classe parente Animal, forçant ainsi une hiérarchie de classe qui ne t'arrangera pas forcément, alors que tout ce qui t'intéresse c'est d'avoir une API commune entre les différents objets de ce tableau, comme savoir combien de pattes ils ont et quel est leur nom.
     

    Du coup, il faudrait imaginer une convention de nommage du type : Je veux une classe Car. Je crée d'abord un protocole Car et ensuite la classe MyCar ou CarImplementation. C'est un peu lourd néanmoins.

    Il y a déjà  une convention suivie par la librairie standard de Swift : en général les protocoles sont suffixés soit de "Type", pour indiquer les caractéristiques liées plutôt à  un type (AnimalType, SequenceType, CollectionType, FloatingPointType, ...) soit de "-ible" ou "able" ou parfois "-er" pour indiquer une "fonctionnalité" / possibilité du type en question (BooleanLiteralConvertible, CustomStringConvertible, Printable). Dans le cas d'une personne et de ses animaux, AnimalType semble mieux convenir, mais dans le cas du protocole permettant d'ajouter la méthode "printError()" à  tes objets comme tes ViewControllers ou autre, le protocole va plutôt porter un nom comme "ErrorPresenter" ou "ErrorRenderer".
     

    - que se passe-t-il si j'override la méthode de l'extension ? Y a-t-il un problème ? J'ai le souvenir d'un passage dans la vidéo où ce n'est pas la même méthode qui est appelée selon le type déclarer de l'instance (ouf, non ?)

    C'est effectivement un peu complexe parfois. A la base, il ne faut pas oublier que les implémentations que tu fournis dans une extension de ton protocole, ce sont des "default implementation". Donc oui, si tu redéfinis l'implémentation de la méthode dans la classe qui se conforme au protocole, l'implémentation de la classe va prendre le pas, puisque l'implémentation que tu as écrit dans ton extension de protocole, c'était l'implémentation *par défaut*, c'est à  dire "au cas où la classe ne fournit pas sa propre implémentation".

    Après, il y a des cas particuliers qui peuvent être piégeux, et qui effectivement semble un peu ouf, en particulier si tu déclares une variable du type de ton protocole, et pas du type de ta classe... il y a un comportement piégeux (qu'il faudrait que je révise d'ailleurs, j'avais fait un GIST sur le sujet pour tester cette subtilité justement)

    - comment indiquer qu'une méthode ne doit pas apparaà®tre dans le pseudo .h ?

    En la déclarant "private", tout simplement. Swift a des "access modifiers" (des mots clés qui permettent d'indiquer le niveau d'accès qu'a chaque méthode), un peu comme en C++ ou Java il me semble. Par défaut toutes les méthodes sont "internal" si tu ne mets rien (visibles uniquement depuis le module), du coup si tu veux les rendre publiques même à  l'extérieur de ton module / ta lib que tu codes, il faut utiliser "public", et si tu veux qu'elles soient privées en dehors du fichier .swift que tu codes, même pour les autres classes qui sont dans le même module, il faut que tu utilises "private".

    Pour plus d'infos, je t'invite à  lire cette entrée du Blog Swift officiel d'Apple.


  • Après, il y a des cas particuliers qui peuvent être piégeux, et qui effectivement semble un peu ouf, en particulier si tu déclares une variable du type de ton protocole, et pas du type de ta classe... il y a un comportement piégeux (qu'il faudrait que je révise d'ailleurs, j'avais fait un GIST sur le sujet pour tester cette subtilité justement)




     


    C'est ouf !

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