如何为Objective-C协议提供默认实现?
题
我想指定具有可选例程的Objective-C协议。当例程未通过符合协议的类实现,我想在其位置使用默认实现。协议本身中是否可以定义此默认实现?如果不是,那么在整个地方都要减少复制和粘贴此默认实现的最佳实践是什么?
解决方案
Objective-C协议对默认实施无力。它们纯粹是可以由其他类实施的方法声明的收集。 Objective -C中的标准实践是在运行时测试对象,以查看在调用该方法之前,使用 - [NSObject rectionStoselector:]。如果E对象未响应给定的选择器,则该方法未调用。
您可以达到的结果的一种方法是定义一种封装您在调用类中寻找的默认行为的方法,如果对象未通过测试,则调用该方法。
另一种方法是使该方法在协议中需要,并在您可能不想提供特定实现的任何类的超类中提供默认实现。
可能还有其他选项,但总的来说,Objective-C中没有特定的标准实践,除非如果对象没有由对象实现,则根据我的第一段,该方法不调用给定方法。
其他提示
没有标准的方法来执行此操作,因为协议不应定义任何实现。
由于Objective-C带有整洁的运行时,因此,如果您真的认为需要这样做,则当然可以添加这样的行为(并且没有可能通过继承实现同样的行为)。
假设您声明了MyProtocol,然后只需在您的协议声明下的.h文件中添加一个带有相同名称的接口:
@interface MyProtocol : NSObject <MyProtocol>
+ (void)addDefaultImplementationForClass:(Class)conformingClass;
@end
并创建一个相应的实现文件(使用 莫布克列赛 在此处可读性,但是标准运行时函数不会更多的代码):
@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]];
在您的类的初始化器中,符合协议,并添加所有默认方法。
真正令人着迷的方法是使用运行时。在启动时,在计划执行的早期,请执行以下操作:
- 列举所有类,查找实施协议的类
- 检查课程是否实现方法
- 如果没有,请添加默认实现
可以在没有太多麻烦的情况下实现。
正如瑞安(Ryan)提到的协议没有默认实现,在超级类中实现的另一个选项是实现一种“处理程序”类,可以包含在任何要提供默认实现的类中,然后调用适当的方法,然后调用默认处理程序实现。
我最终创建了一个具有该方法默认实现的宏。
我已经在协议的标题文件中定义了它,然后在每个实现中只是一个单线。
这样,我就不必更改实施几个位置,并且在编译时间完成了,因此无需运行时魔术。
我同意“ WM”一个非常好的解决方案是将所有默认实现放入接口(具有与协议相同的名称)中。在任何子类的“+初始化”方法中,它可以简单地将默认接口中的任何未完成方法复制到自身中。
以下助手功能对我有用
#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缺少方法的警告,但您可以忽略它们。
该技术仅复制方法,而不是IVAR。如果这些方法正在访问不存在的数据成员,则可能会得到奇怪的错误。您必须确保数据与代码兼容。好像您从Foobar到MyProtocol进行了Reinterpret_cast。