23

我想指定一个带有可选例程的 Objective-C 协议。当例程不是由符合协议的类实现时,我想使用默认实现来代替它。协议本身是否有一个地方可以定义这个默认实现?如果不是,那么减少到处复制和粘贴此默认实现的最佳做法是什么?

4

6 回答 6

19

Objective-C 协议无法提供默认实现。它们纯粹是可以由其他类实现的方法声明的集合。Objective-C 中的标准做法是在运行时测试一个对象,看看它是否响应给定的选择器,然后再调用它的方法,使用 -[NSObject respondsToSelector:]。如果 e 对象没有响应给定的选择器,则不会调用该方法。

您可以实现您正在寻找的结果的一种方法是定义一个方法来封装您在调用类中寻找的默认行为,并在对象未通过测试时调用该方法。

另一种方法是使协议中需要该方法,并在您可能不想提供特定实现的任何类的超类中提供默认实现。

可能还有其他选项,但一般来说,Objective-C 中没有特定的标准实践,除非根据我上面的第一段,如果对象没有实现给定的方法,则可能不调用它.

于 2010-12-02T01:06:22.900 回答
17

There is no standard way for doing that as protocols should not define any implementations.

Since Objective-C comes with a neat runtime, you can of course add such a behavior if you really think you need to do it that way (and there's no possibility by achieving the same with inheritance).

Say you declared MyProtocol, then just add an interface with the same name in the .h file under your protocol declaration:

@interface MyProtocol : NSObject <MyProtocol>

+ (void)addDefaultImplementationForClass:(Class)conformingClass;

@end

And create a corresponding implementation file (using MAObjCRuntime for readability here, but the standard runtime functions wouldn't be much more code):

@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
}

Then you just have to call

[MyProtocol addDefaultImplementationForClass:[self class]];

in the initializer of your class conforming to the protocol and all default methods will be added.

于 2010-12-02T01:48:43.867 回答
4

A truly fascinating way is to use the runtime. At the start-up, very early in the program execution, do the following:

  1. Enumerate all the classes, find classes which implement the protocol
  2. Check if the class implements a method
  3. If not, add to the class the default implementation

It can be achieved without that much trouble.

于 2010-12-02T01:47:10.437 回答
2

我同意“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 缺少方法的警告,但你可以忽略它们。

这种技术只复制方法,而不是 ivars。如果这些方法正在访问不存在的数据成员,您可能会遇到奇怪的错误。您必须确保数据与代码兼容。就好像你做了一个从 Foobar 到 MyProtocol 的 reinterpret_cast。

于 2014-04-14T17:44:26.163 回答
1

As Ryan mention there are no default implementations for protocols, another option to implementing in the superclass would be is to implement a "Handler" kind of class that can be contained in any class that want to provide the default implementation, the appropriate method then calls the default handlers implementation.

于 2010-12-02T01:49:11.323 回答
1

我最终创建了一个具有该方法的默认实现的宏。

我已经在协议的头文件中定义了它,然后在每个实现中它只是一个单行。

这样,我不必更改几个地方的实现,而且它是在编译时完成的,所以不需要运行时魔法。

于 2014-02-25T13:20:46.413 回答