Как обеспечить реализацию по умолчанию для протокола 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.