我想指定一个具有可选例程的 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]];
在符合协议的类的初始化程序中,将添加所有默认方法。
一个真正令人着迷的方法是使用运行时。在启动时,在程序执行的早期,执行以下操作:
无需那么麻烦就可以实现。
我同意“w.m”。一个非常好的解决方案是将所有默认实现放入一个接口中(与协议同名)。在任何子类的“+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 缺少方法的警告,但您可以忽略它们。
此技术仅复制方法,而不复制 ivars。如果这些方法正在访问不存在的数据成员,您可能会遇到奇怪的错误。您必须确保数据与代码兼容。就好像您从 Foobar 到 MyProtocol 进行了reinterpret_cast。
正如 Ryan 提到的,协议没有默认实现,在超类中实现的另一个选择是实现一个“Handler”类型的类,该类可以包含在任何想要提供默认实现的类中,然后提供适当的方法调用默认处理程序实现。
我最终创建了一个具有该方法的默认实现的宏。
我已经在协议的头文件中定义了它,然后它只是每个实现中的一行。
这样,我就不必更改实现的几个地方,并且它是在编译时完成的,因此不需要运行时魔法。