Как предоставить реализацию по умолчанию для протокола Objective C?
Я хотел бы указать протокол Objective C с дополнительной процедурой. Когда подпрограмма не реализована классом, соответствующим протоколу, я бы хотел использовать вместо него реализацию по умолчанию. Есть ли место в самом протоколе, где я могу определить реализацию по умолчанию? Если нет, что является лучшим способом уменьшить копирование и вставку этой реализации по умолчанию повсюду?
6 ответов
Протоколы Objective C не имеют возможности для реализации по умолчанию. Они являются просто коллекциями объявлений методов, которые могут быть реализованы другими классами. Стандартная практика в Objective-C состоит в том, чтобы тестировать объект во время выполнения, чтобы увидеть, отвечает ли он на данный селектор, прежде чем вызывать этот метод, используя -[NSObject RespondsToSelector:]. Если объект не отвечает на данный селектор, метод не вызывается.
Одним из способов достижения результата, который вы ищете, является определение метода, инкапсулирующего поведение по умолчанию, которое вы ищете в вызывающем классе, и вызов этого метода, если объект не проходит тест.
Другой подход заключается в том, чтобы сделать метод обязательным в протоколе и обеспечить реализации по умолчанию в суперклассах любых классов, в которых вы можете не захотеть предоставлять конкретную реализацию.
Возможно, есть и другие варианты, но, вообще говоря, в Objective-C нет конкретной стандартной практики, за исключением, возможно, просто не вызова данного метода, если он не был реализован объектом, согласно моему первому абзацу выше.,
Не существует стандартного способа сделать это, поскольку протоколы не должны определять какие-либо реализации.
Поскольку 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]];
в инициализаторе вашего класса, соответствующего протоколу, и все методы по умолчанию будут добавлены.
По-настоящему увлекательный способ - использовать среду выполнения. При запуске, в самом начале выполнения программы, сделайте следующее:
- Перечислите все классы, найдите классы, которые реализуют протокол
- Проверьте, реализует ли класс метод
- Если нет, добавьте в класс реализацию по умолчанию
Это может быть достигнуто без особых проблем.
Я согласен с "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.
Как отметил Райан, реализации протоколов по умолчанию не существует, другой вариант реализации в суперклассе заключается в реализации класса "Обработчик", который может содержаться в любом классе, который хочет обеспечить реализацию по умолчанию, после чего соответствующий метод вызывает реализация обработчиков по умолчанию.
Я закончил тем, что создал макрос, который имеет реализацию метода по умолчанию.
Я определил это в заголовочном файле протокола, и тогда это просто строка в каждой реализации.
Таким образом, мне не нужно менять реализацию в нескольких местах, и это делается во время компиляции, поэтому никакой магии во время выполнения не требуется.