Méthodes de classes vs (Shared instance + Méthodes d'instance)
colas_
Membre
Bonjour,
je suis en train de créer une classe "assistante" pour Core Graphics, avec des méthodes comme :
+ (CGPoint)centerOf:(CGRect)mainRect ;
+ (CGRect)rectWithWidth:(CGFloat)width
withHeight:(CGFloat)height
withCenterX:(CGFloat)centerX
withCenterY:(CGFloat)centerY ;
Cependant, j'aurais pu choisir de déclarer ces méthodes en méthodes d'instance :
- (CGPoint)centerOf:(CGRect)mainRect ;
- (CGRect)rectWithWidth:(CGFloat)width
withHeight:(CGFloat)height
withCenterX:(CGFloat)centerX
withCenterY:(CGFloat)centerY ;
et de faire de ma classe une shared instance.
Je pense que la méthode de la shared instance est meilleure car elle est plus souple. Qui sait, un jour j'aurai peut-être besoin de paramètres !
Et vous, qu'en pensez-vous ?
Merci
Colas
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
1) Non, une sharedInstance n'est à créer que quand c'est vraiment nécessaire conceptuellement.
Là tu veux grouper des méthodes utilitaires ensemble, méthodes qui sont stateless (ne dépendent pas d'un état interne, et sont déterministes). Un peu comme une sorte de namespace pour regrouper des fonctions indépendantes ensemble dans un groupement logique.
Donc clairement des méthodes de classes suffisent, et pas de sharedInstance.
2) Et en + et surtout, si vraiment un jour tu as besoin de convertir ta classe utilitaire en sharedInstance, il ne faudra pas pour autant convertir ces méthodes de classe en méthode d'instance. Non. C'est bien mieux quand on faire une sharedInstance "parce qu'on a besoin d'une variable interne qui garde un état", de garder toute l'API publique sous forme de méthodes de classes et de continuer à pouvoir appeler ces méthodes directement sur la classe.
- Pour les méthodes qui ne nécessitent aucunement l'utilisation de l'état interne, comme les méthodes déjà écrites avant la conversion en sharedInstance, le code ne changera pas.
- Pour les méthodes qui nécessitent vraiment d'utiliser la sharedInstance pour accéder à ses @property et son état, tu peux toujours faire des méthodes de classe, qui en interne utilisent "self.sharedInstance" pour accéder à la sharedInstance et à ses propriétés.
- Tu peux même aller jusqu'à (et c'est souvent ce que je fais pour mes classes de type "Service") mettre ta méthode "+(instancetype)sharedInstance" en privée (uniquement dans le .m, non déclarée dans le .h) puisque du coup dans le .h tu n'auras que des méthodes de classe et que si certaines méthodes nécessitent un accès à la sharedInstance dans leur implémentation, ça ne sera que utilisé qu'en interne dans le .m, c'est un détail d'implémentation
3) Enfin, pour ton cas bien précis, je trouve que ce n'est pas forcément utile de faire le wrapper que tu es en train de faire. Il existe déjà des fonctions qui font très bien l'affaire, comme CGRectMake(x,y,width,height) et du coup si tu veux étendre l'idée je te conseille plutôt de faire également des fonctions globales. Comme écrire une fonction CGRectMakeFromCenter(centerX, centerY, width, height) par exemple.
Non seulement ça resterai dans la continuité des autres fonctions existantes, et en plus pour la création de structures ou ce genre de petites tâches, l'utilisation de fonctions est plus adapté car c'est plus "direct" et évite un appel objet (objc_msgSend).
- Le pattern sharedInstance est à éviter tant qu'il n'est pas justifié
- Utiliser une sharedInstance par définition ça crée quand même une instance, donc c'est de l'allocation d'un objet (alloc/init)... dans ton cas totalement inutile puisqu'il ne t'apporte rien.
- Donc entre l'allocation de ton instance pour rien, le mécanisme de message forwarding utilisé (car appel de méthode ObjC plutôt qu'appel de fonction C), tout ça pour un tout petit truc utilitaire qui ne justifie pas spécialement tout ce bazar et cette instanciation de mécanique pour rien
Je n'ai encore jamais créé de fonction, donc ça va être l'occasion de m'y mettre !
Est-ce que ce qui suit est ok ?
et
Pas besoin de mot clé extern ou const ?
Si j'ai des objets en paramètres ou en retour, c'est tout pareil ?
Merci !
Est-ce que je peux avoir un .h et un .m juste dédiés à ces fonctions (pas de @interface, etc.) ?
Une category sur UIView (par exemple), ça s'rait pas plus intéressant ?
Voici du coup ce que je fais (j'en profite pour partager ces petites fonctions)
puis
D'après ce que dit Ali, non car on utilise inutilement le mécanisme d'envoi de message.
Par ailleurs, avec ta façon de faire, ces méthodes/fonctions ne seraient pas accessible sur OSX.... (je ne sais pas si tu fais du OSX)
Pour moi, en tout cas, c'est l'occasion d'apprendre les fonctions !
Je me dis que même si ça ne va pas dans le sens de mes opinions, ça me permet d'apprendre quelque chose de nouveau et ensuite je pourrai changer d'avis !!!
Merci en tout cas pour votre aide (spéciale dédicace à Ali ---> j'ai habité 6 ans à Rennes, j'y repasse régulièrement, je te dois bien une bière " si ce n'est deux) !
Colas
Non, pas besoin. "extern" est un mot clé pour dire "je ne fais que déclarer que le truc qui suit existe, mais je n'en donne pas sa valeur/définition".
- Dans le cas d'une variable "extern int toto" dit juste au compilateur "y'a une variable qqpart qui existe et s'appelle toto, je la définis pas ici, mais sache qu'elle existe". Ce qui permet au compilo de ne pas être surpris et de ne pas te sortir d'erreur s'il voir du code qui utilise cette variable toto. (Et c'est lors de l'édition de liens " assemblage du produit de la compilation de chacun de tes fichiers en un seul binaire " que, si nulle part dans aucun des fichiers la variable toto n'a été effectivement définie, avec une valeur et tout, qu'il va gueuler disant que cette soit-disant variable n'existe nulle part)
- Dans le cas d'une fonction, comme dans le ".h" tu ne donnes pas de corps à cette fonction (juste le nom suivi d'un point-virgule, sans l'implémentation), alors il est clair que le but est juste de déclarer ici que la fonction existe (avec telle signature et tel type de retour) et du coup le mot "extern" est implicite dans le standard C.
Tu pourrais tout à fait rajouter "extern" devant la déclaration de ta fonction si tu veux, mais ce n'est pas la peine car le compilateur fait comme s'il était là vu que dans ton .h ta fonction n'est que déclarée, pas définie (pas de corps, qui n'est que dans le .m). C'est le comportement par défaut pour les fonctions, et du coup en général on ne met pas ce mot clé "extern" dans ce cas car il serait un peu redondantOui, tout pareil !
Oui, tout à fait. Je te le conseille, même, histoire de bien isoler les choses.
En quoi ces fonctions sont-elles rattachées à UIView ? Elles ne manipulent pas du tout du UIView, juste des CGRect, CGPoint, etc...
C'est comme si tu disais d'implémenter une méthode de classe "+stringWithInt:" qui crée un NSString à partir d'un int... sur la classe NSArray ou NSDictionary ^^
Alors oui certes les UIView ont une frame, qui est un CGRect, mais le rapprochement s'arrête là . Dans le cas (qui est d'ailleurs le cas de colas2) où tu veux utiliser ces fonctions de manipulation de CGRect pour faire du dessin CoreGraphics, que ce soit dans le drawRect d'une UIView ou dans une implémentation de CALayer ou dans un UIBitmapGraphicContext ou même avec des vues OSX, bah là y'a plus trop de rapport avec une UIView et sa frame...
D'une part parce qu'autant utiliser ce qui existe déjà , et d'autre part parce que ces méthodes font peut-être des choses et traitent peut-être des cas auxquels tu n'as pas forcément pensé (genre quid si la width ou la height est négative, est-ce que ça marche toujours, quid des erreurs d'arrondi, etc). En utilisant CGRectGetMidX/Y au moins tu n'as pas à te poser la question de savoir si ton calcul est bon, c'est la fonction Apple qui s'en charge.
C'est ce que fait Apple, par exemple sur CGRectMake().
Ca m'a toujours fasciné d'ailleurs à quel point le compilo devient plus intelligent que nous pour décider s'il doit mettre inline ou pas...
C'est pour ça qu'il y a aussi l'attribut always_inline qui existe pour forcer une fonction à être inline même si le compilateur jugeait que, malgré ton mot clé inline, il préférait la garder en vraie fonction). Mais en pratique, c'est toujours déconseillé de se croire plus intelligent que le compilateur, mieux vaut le laisser décider tout seul il sait mieux que nous quel est le plus bénéfique