我想指定一个带有可选方法的Objective-C协议。当遵循该协议的类没有实现该方法时,我希望使用一个默认实现来代替它。在协议本身中是否有定义这个默认实现的位置?如果没有,最佳实践是什么,以减少在各处复制粘贴此默认实现?
我想指定一个带有可选方法的Objective-C协议。当遵循该协议的类没有实现该方法时,我希望使用一个默认实现来代替它。在协议本身中是否有定义这个默认实现的位置?如果没有,最佳实践是什么,以减少在各处复制粘贴此默认实现?
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]];
在符合该协议的类的初始化程序中,所有默认方法将被添加。
一种非常有趣的方法是使用运行时。在程序执行的早期阶段,可以执行以下操作:
这个过程可以很容易地完成。
#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缺少方法,但可以忽略这些警告。
此技术仅复制方法,而不是实例变量。如果这些方法访问不存在的数据成员,则可能导致奇怪的错误。您必须确保数据与代码兼容。就像从Foobar重新解释为MyProtocol一样。