Méthodes de classes vs (Shared instance + Méthodes d'instance)

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


Réponses

  • AliGatorAliGator Membre, Modérateur
    Alors :

    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).
  • AliGatorAliGator Membre, Modérateur
    Ah et last but not least :
    - 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 ;)
  • colas_colas_ Membre
    juin 2014 modifié #4
    Très intéressant ! Merci !
     
    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 ?
    //MyFunctions.h

    CGPoint CGPointCenterOf(CGRect mainRect) ;

    et 
    //MyFunctions.m

    CGPoint CGPointCenterOf(CGRect rect)
    {
    CGPoint result = CGPointMake(mainRect.origin.x + (mainRect.size.width / 2),
    mainRect.origin.y + (mainRect.size.height / 2));
    return result ;
    }
     
    Pas besoin de mot clé extern ou const ?
    Si j'ai des objets en paramètres ou en retour, c'est tout pareil ?
     
    Merci !
  • colas_colas_ Membre
    juin 2014 modifié #5

    Est-ce que je peux avoir un .h et un .m juste dédiés à  ces fonctions (pas de @interface, etc.) ?


  • LarmeLarme Membre
    juin 2014 modifié #6

    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)



    //
    // CBDCoreGraphicsHelper.h
    // Pods
    //
    // Created by Colas on 19/06/2014.
    //
    //


    #import <Foundation/Foundation.h>
    #import <CoreGraphics/CoreGraphics.h>


    //
    //
    /**************************************/
    #pragma mark - Center
    /**************************************/

    CGPoint CGPointGetCenter_cbd_(CGRect rect) ;
    CGRect CGRectMake_byGivingCenter_cbd_(CGFloat width, CGFloat height, CGFloat centerX, CGFloat centerY) ;

    //
    //
    /**************************************/
    #pragma mark - Scaling NSSize
    /**************************************/

    CGSize CGSizeMakeByScaling_cbd_(CGSize size, CGFloat scaleFactor) ;
    CGSize CGSizeMakeByScaling_maxwise_cbd_(CGSize size, CGSize container) ;
    CGSize CGSizeMakeByScaling_heightwise_cbd_(CGSize size, CGFloat height) ;
    CGSize CGSizeMakeByScaling_widthwise_cbd_(CGSize size, CGFloat width) ;


    //
    //
    /**************************************/
    #pragma mark - Enlarge GCRect
    /**************************************/

    CGRect CGRectMakeByEnlarging_cbd_(CGRect rect, CGFloat enlargingLenth, CGFloat scaleFactor) ;


    puis




    //
    // CBDCoreGraphicsHelper.c
    // Pods
    //
    // Created by Colas on 19/06/2014.
    //
    //

    #include "CBDCoreGraphicsHelper.h"



    //
    //
    /**************************************/
    #pragma mark - Center
    /**************************************/

    CGPoint CGPointGetCenter_cbd_(CGRect rect)
    {
    return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)) ;
    }


    CGRect CGRectMake_byGivingCenter_cbd_(CGFloat width, CGFloat height, CGFloat centerX, CGFloat centerY)
    {
    CGRect result ;

    result.size.width = width ;
    result.size.height = height ;

    result.origin.x = centerX - width/2 ;
    result.origin.y = centerY - height/2 ;

    return result ;

    }






    //
    //
    /**************************************/
    #pragma mark - Scaling NSSize
    /**************************************/

    CGSize CGSizeMakeByScaling_cbd_(CGSize size, CGFloat scaleFactor)
    {
    CGSize result ;

    if (scaleFactor < 0)
    {
    scaleFactor = - scaleFactor ;
    }

    result.width = size.width*scaleFactor ;
    result.height = size.height*scaleFactor ;

    return result ;
    }


    CGSize CGSizeMakeByScaling_maxwise_cbd_(CGSize mainSize, CGSize container)
    {
    /*
    We deal with the zero case
    */
    if (!mainSize.width
    ||
    !mainSize.height)
    {
    return (CGSize){0,0} ;
    }


    /*
    General case
    */

    CGSize result ;

    CGFloat factorForFittingWidth = container.width / mainSize.width ;
    CGFloat newHeight = mainSize.height * factorForFittingWidth ;
    if (newHeight <= container.height)
    {
    result = CGSizeMakeByScaling_cbd_(mainSize, factorForFittingWidth) ;
    }
    else
    {
    result = CGSizeMakeByScaling_cbd_(mainSize, container.height / mainSize.height) ;
    }

    return result ;

    }



    CGSize CGSizeMakeByScaling_heightwise_cbd_(CGSize size, CGFloat height)
    {
    if (size.height == 0)
    {
    return CGSizeZero ;
    }
    else
    {
    return CGSizeMakeByScaling_cbd_(size, height/size.height) ;
    }
    }


    CGSize CGSizeMakeByScaling_widthwise_cbd_(CGSize size, CGFloat width)
    {
    if (size.width == 0)
    {
    return CGSizeZero ;
    }
    else
    {
    return CGSizeMakeByScaling_cbd_(size, width/size.width) ;
    }
    }





    //
    //
    /**************************************/
    #pragma mark - Enlarge GCRect
    /**************************************/

    CGRect CGRectMakeByEnlarging_cbd_(CGRect rect, CGFloat enlargingLenth, CGFloat scaleFactor)
    {
    // relativeLength = relativeLength * factor ;
    //
    // CGRect result ;
    // result.origin.x = initialRect.origin.x - relativeLength ;
    // result.origin.y = initialRect.origin.y - relativeLength ;
    // result.size.width = initialRect.size.width + 2*relativeLength ;
    // result.size.height = initialRect.size.height + 2*relativeLength ;
    //
    // return result ;

    /*
    Thank you

    http://blogs.oreilly.com/iphone/2008/12/useful-core-graphics-functions.html
    */

    return CGRectInset(rect, -enlargingLenth*scaleFactor, - enlargingLenth*scaleFactor) ;
    }



  • Une category sur UIView (par exemple), ça s'rait pas plus intéressant ?




     


    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

  • AliGatorAliGator Membre, Modérateur
    juin 2014 modifié #9

    Est-ce que ce qui suit est ok ?

    Oui. Il n'y a que côté conventions de nommage de ta fonction qu'il serait bon de changer, pour suivre les fonctions de CGGeometry. J'aurais ainsi plutôt nommé ta fonction CGRectGetCenter() que CGPointCenterOf(). Les conventions de nommage des fonctions C sont un peu différentes que l'ObjC, de par leur syntaxe (en particulier l'absence de paramètres nommés)
     

    Pas besoin de mot clé extern ou const ?

    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 redondant
     

    Si j'ai des objets en paramètres ou en retour, c'est tout pareil ?

    Oui, tout pareil !

     

    Est-ce que je peux avoir un .h et un .m juste dédiés à  ces fonctions (pas de @interface, etc.) ?

    Oui, tout à  fait. Je te le conseille, même, histoire de bien isoler les choses.
  • AliGatorAliGator Membre, Modérateur

    Une category sur UIView (par exemple), ça s'rait pas plus intéressant ?

    Heu non, et en plus ça ne serait pas du tout adapté !
    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...
  • AliGatorAliGator Membre, Modérateur
    Ah et aussi, dans tes implémentations de ces fonctions, utilise plutôt les fonctions de CGGeometry déjà  existantes. Genre les fonctions CGRectGetMidX() et CGRectGetMidY(). Plutôt que de recalculer to-même le center via x+width/2 et y+height/2.

    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éroceCéroce Membre, Modérateur
    Et déclare tes fonctions en "inline". Quand on utilise ce mot-clef, la fonction n'est pas appelée, mais son code est automatiquement replacé dans le code appelant, ce qui améliore les performances: pas d'appel de fonction, donc pas de variables à  empiler.

    C'est ce que fait Apple, par exemple sur CGRectMake().
  • colas_colas_ Membre
    juin 2014 modifié #13
    Pourquoi toutes les fonctions ne sont pas inline dans ce cas ?
  • CéroceCéroce Membre, Modérateur
    Parce que ça duplique le code, donc l'exécutable deviendrait énorme! De plus, ça interdit certaines constructions, comme la récursivité. Je ne suis pas sûr qu'on puisse mettre un point d'arrêt dedans, non plus.
  • AliGatorAliGator Membre, Modérateur
    D'ailleurs le inline est un bon conseil... mais en pratique les compilateurs maintenant sont tellement intelligents qu'ils savent détecter quand ça vaut + le coup de mettre la fonction inline ou pas (grâce à  des algos de prédiction et de stats assez poussé)

    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
Connectez-vous ou Inscrivez-vous pour répondre.