Как предоставить реализацию по умолчанию для протокола Objective-C? - PullRequest
21 голосов
/ 02 декабря 2010

Я бы хотел указать протокол Objective C с необязательной подпрограммой.Когда подпрограмма не реализована классом, соответствующим протоколу, я бы хотел использовать вместо него реализацию по умолчанию.Есть ли место в самом протоколе, где я могу определить реализацию по умолчанию?Если нет, то какова лучшая практика для уменьшения копирования и вставки этой реализации по умолчанию повсюду?

Ответы [ 6 ]

18 голосов
/ 02 декабря 2010

Протоколы Objective-C не предусматривают реализации по умолчанию. Они являются просто коллекциями объявлений методов, которые могут быть реализованы другими классами. Стандартная практика в Objective-C состоит в том, чтобы проверить объект во время выполнения, чтобы увидеть, отвечает ли он на данный селектор, прежде чем вызывать этот метод, используя - [NSObject responsedsToSelector:]. Если объект не отвечает на данный селектор, метод не вызывается.

Одним из способов достижения результата, который вы ищете, было бы определение метода, инкапсулирующего поведение по умолчанию, которое вы ищете в вызывающем классе, и вызов этого метода, если объект не прошел тест.

Другой подход заключается в том, чтобы сделать метод обязательным в протоколе и обеспечить реализации по умолчанию в суперклассах любых классов, в которых вы, возможно, не захотите предоставлять конкретную реализацию.

Возможно, есть и другие варианты, но, вообще говоря, в Objective-C нет конкретной стандартной практики, кроме, возможно, просто не вызывать данный метод, если он не был реализован объектом, согласно моему первому абзац выше.

17 голосов
/ 02 декабря 2010

Нет стандартного способа сделать это, поскольку протоколы не должны определять какие-либо реализации.

Поскольку Objective-C поставляется с аккуратным временем выполнения, вы, конечно, можете добавить такое поведение, если действительно считаете, что вам нужно сделать это таким образом (и нет возможности достичь того же с помощью наследования).

Скажем, вы объявили MyProtocol, затем просто добавьте интерфейс с тем же именем в файл .h под объявлением протокола:

@interface MyProtocol : NSObject <MyProtocol>

+ (void)addDefaultImplementationForClass:(Class)conformingClass;

@end

И создайте соответствующий файл реализации (для удобства чтения используйте MAObjCRuntime , но стандартные функции времени выполнения не будут намного больше кода):

@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
}

Тогда вам просто нужно позвонить

[MyProtocol addDefaultImplementationForClass:[self class]];

в инициализаторе вашего класса, соответствующего протоколу, и все методы по умолчанию будут добавлены.

4 голосов
/ 02 декабря 2010

Действительно увлекательный способ - использовать среду выполнения. При запуске, в самом начале выполнения программы, сделайте следующее:

  1. Перечислить все классы, найти классы, которые реализуют протокол
  2. Проверьте, реализует ли класс метод
  3. Если нет, добавьте в класс реализацию по умолчанию

Это может быть достигнуто без особых проблем.

1 голос
/ 14 апреля 2014

Я согласен с "wm". Очень хорошее решение - поместить все реализации по умолчанию в интерфейс (с тем же именем, что и протокол).В методе «+ initialize» любого подкласса он может просто скопировать в себя любые не реализованные методы из интерфейса по умолчанию.

Следующие вспомогательные функции сработали для меня

#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);
}

Затем вы вызываете егов вашем инициализаторе класса, таком как ...

@interface Foobar : NSObject<MyProtocol>  
@end

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

Xcode выдаст вам предупреждения об отсутствующих методах Foobar, но вы можете их игнорировать.

Эта техника копирует только методы, а не ивары.Если методы обращаются к несуществующим элементам данных, вы можете получить странные ошибки.Вы должны убедиться, что данные совместимы с кодом.Это как если бы вы сделали reinterpret_cast из Foobar в MyProtocol.

1 голос
/ 25 февраля 2014

Я закончил тем, что создал макрос, который имеет реализацию метода по умолчанию.

Я определил его в заголовочном файле протокола, и тогда в каждой реализации это просто по одной строке.

Таким образом, мне не нужно менять реализацию в нескольких местах, и это делается во время компиляции, поэтому магия во время выполнения не требуется.

1 голос
/ 02 декабря 2010

Как отметил Райан, реализации протоколов по умолчанию не существует, другой вариант реализации в суперклассе - реализовать класс «Обработчик», который может содержаться в любом классе, который хочет обеспечить реализацию по умолчанию, соответствующую Затем метод вызывает реализацию обработчиков по умолчанию.

...