Как обеспечить реализацию по умолчанию для протокола Objective-C?

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

Ответы

Ответ 1

Протоколы

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

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

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

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

Ответ 2

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

Так как 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]];

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

Ответ 3

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

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

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

Ответ 4

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

Ответ 5

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

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

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

Ответ 6

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

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

#import <objc/runtime.h>

// Get the type string of a method, such as "[email protected]:".
// 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, но вы можете их игнорировать.

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