16

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

+2

Этот пост предлагает элегантное, новое решение: используйте категорию в NSObject. http://stackoverflow.com/questions/19329309/whats-wrong-with-using-a-category-on-nsobject-to-provide-a-default-protocol-imp?rq=1 – 2014-04-17 13:25:35

ответ

13

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

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

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

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

4

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

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

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

+1

Как вы смеете называть это хакерским ;) – 2010-12-02 01:50:03

+5

Я действительно написал полный модуль общественного домена, чтобы сделать именно это. Это не так хакально, как кажется, так как использует полностью поддерживаемые и документированные функциональные возможности и тщательно протестировано в производственном коде. http://code.google.com/p/libextobjc/source/browse/extobjc/Modules/EXTConcreteProtocol.h – 2010-12-02 01:50:16

+0

OK Я заменяю «хакерский» на «увлекательный». Это удовлетворит всех вас? – Yuji 2010-12-02 02:18:38

14

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

Поскольку 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]]; 

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

1

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

1

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

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

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

1

Я согласен с "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 отсутствующих методов, но вы может игнорировать их.

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

Смежные вопросы