Question

Je voudrais préciser un protocole Objective-C avec une routine en option. Lorsque la routine n'est pas mis en œuvre par une classe conforme au protocole que je voudrais utiliser une implémentation par défaut à sa place. Y at-il une place dans le protocole lui-même où je peux définir cette implémentation par défaut? Sinon, quelle est la meilleure pratique pour réduire copier et coller cette implémentation par défaut dans tous les sens?

Était-ce utile?

La solution

protocoles Objective-C ont pas affordance pour les implémentations par défaut. Ils sont purement collections de déclarations de méthodes qui peuvent être mises en œuvre par d'autres classes. La pratique courante en Objective-C est de tester un objet lors de l'exécution pour voir si elle répond au sélecteur donné avant d'appeler cette méthode sur elle, en utilisant - [NSObject respondsToSelector]. Si l'objet e ne répond pas au sélecteur donné, la méthode est pas appelée.

Une façon d'obtenir le résultat que vous êtes à la recherche serait de définir une méthode d'encapsulation le comportement par défaut que vous cherchez dans la classe appelant et appeler cette méthode si l'objet ne passe pas le test.

Une autre approche serait de rendre la méthode nécessaire dans le protocole, et de fournir des implémentations par défaut dans les superclasse de toutes les classes où vous voudrez peut-être de ne pas fournir une implémentation spécifique.

Il y a probablement d'autres options aussi, mais de façon générale, il n'y a pas une pratique courante notamment en Objective-C, à l'exception peut-être tout simplement pas appeler la méthode donnée si elle n'a pas été mise en œuvre par l'objet, par mon premier paragraphe, ci-dessus.

Autres conseils

Il n'y a aucun moyen standard pour le faire que les protocoles ne doivent pas définir les implémentations.

Depuis Objective-C est livré avec un moteur d'exécution propre, vous pouvez d'ajouter bien sûr un tel comportement si vous pensez vraiment que vous devez le faire de cette façon (et il n'y a pas de possibilité de réaliser la même chose avec l'héritage).

Dites que vous avez déclaré MyProtocol, puis il suffit d'ajouter une interface avec le même nom dans le fichier .h sous votre déclaration de protocole:

@interface MyProtocol : NSObject <MyProtocol>

+ (void)addDefaultImplementationForClass:(Class)conformingClass;

@end

Et créer un fichier d'implémentation correspondant (en utilisant MAObjCRuntime pour une meilleure lisibilité, mais les fonctions d'exécution standards Wouldn » t être un code beaucoup plus):

@implementation MyProtocol

+ (void)addDefaultImplementationForClass:(Class)conformingClass {
  RTProtocol *protocol = [RTProtocol protocolWithName:@"MyProtocol"];
  // get all optional instance methods
  NSArray *optionalMethods = [protocol methodsRequired:NO instance:YES];
  for (RTMethod *method in optionalMethods) {
    if (![conformingClass rt_methodForSelector:[method selector]]) {
      RTMethod *myMethod = [self rt_methodForSelector:[method selector]];
      // add the default implementation from this class
      [conformingClass rt_addMethod:myMethod];
    }
  }
}

- (void)someOptionalProtocolMethod {
  // default implementation
  // will be added to any class that calls addDefault...: on itself
}

Il vous suffit d'appeler

[MyProtocol addDefaultImplementationForClass:[self class]];

dans la initialiseur de votre classe conforme au protocole et toutes les méthodes par défaut seront ajoutés.

Une façon vraiment fascinante est d'utiliser le moteur d'exécution. Au démarrage, très tôt dans l'exécution du programme, procédez comme suit:

  1. Enumérer toutes les classes, trouver des cours qui mettent en œuvre le protocole
  2. Vérifiez si les outils de classe une méthode
  3. Dans le cas contraire, ajouter à la classe l'implémentation par défaut

Il peut être réalisé sans que beaucoup de peine.

Ryan mentionne qu'il n'y a pas mises en œuvre par défaut des protocoles, une autre option pour la mise en œuvre dans la superclasse serait consiste à mettre en place un « gestionnaire » sorte de classe qui peut être contenue dans une classe qui veulent fournir l'implémentation par défaut, le approprié méthode appelle ensuite les gestionnaires par défaut la mise en œuvre.

J'ai fini par créer une macro qui a une implémentation par défaut de la méthode.

Je l'ai défini dans le fichier d'en-tête du protocole, et il est juste un dans chaque mise en œuvre d'une doublure.

De cette façon, je n'ai pas de changer la mise en œuvre de plusieurs endroits, et il a fait le moment de la compilation, donc pas la magie d'exécution est nécessaire.

Je suis d'accord avec "W.M." Une solution très agréable est de mettre toutes les implémentations par défaut dans une interface (avec le même nom que le protocole). Dans la méthode « + initialize » de toute sous-classe, il peut simplement copier les méthodes inappliquées de l'interface par défaut en lui-même.

Les fonctions d'aide suivantes travaillé pour moi

#import <objc/runtime.h>

// Get the type string of a method, such as "v@:".
// Caller must allocate sufficent space. Result is null terminated.
void getMethodTypes(Method method, char*result, int maxResultLen)
{
    method_getReturnType(method, result, maxResultLen - 1);
    int na = method_getNumberOfArguments(method);
    for (int i = 0; i < na; ++i)
    {
        unsigned long x = strlen(result);
        method_getArgumentType(method, i, result + x, maxResultLen - 1 - x);
    }
}

// This copies all the instance methods from one class to another
// that are not already defined in the destination class.
void copyMissingMethods(Class fromClass, Class toClass)
{
    // This gets the INSTANCE methods only
    unsigned int numMethods;
    Method* methodList = class_copyMethodList(fromClass, &numMethods);
    for (int i = 0; i < numMethods; ++i)
    {
        Method method = methodList[i];
        SEL selector = method_getName(method);
        char methodTypes[50];
        getMethodTypes(method, methodTypes, sizeof methodTypes);

        if (![toClass respondsToSelector:selector])
        {
            IMP methodImplementation = class_getMethodImplementation(fromClass, selector);
            class_addMethod(toClass, selector, methodImplementation, methodTypes);
        }
    }
    free(methodList);
}

Ensuite, vous l'appelez dans votre classe initialiseur tels que ...

@interface Foobar : NSObject<MyProtocol>  
@end

@implementation Foobar
+(void)initialize
{
    // Copy methods from the default
    copyMissingMethods([MyProtocol class], self);
}
@end

Xcode vous donnera des avertissements sur les méthodes manquantes Foobar, mais vous pouvez les ignorer.

Cette technique uniquement des méthodes exemplaires, non Ivars. Si les méthodes accèdent à des membres de données qui n'existent pas, vous pourriez obtenir des bugs étranges. Vous devez vous assurer que les données sont compatibles avec le code. Il est comme si vous avez fait un reinterpret_cast de Foobar à MyProtocol.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top