如何为 Objective-C 协议提供默认实现?

问题描述 投票:0回答:6

我想指定一个具有可选例程的 Objective-C 协议。当例程不是由符合协议的类实现时,我想在其位置使用默认实现。协议本身是否有一个地方可以定义这个默认实现?如果没有,减少到处复制和粘贴此默认实现的最佳实践是什么?

objective-c protocols standards-compliance default-implementation overhead-minimization
6个回答
19
投票

Objective-C 协议无法提供默认实现。 它们纯粹是可以由其他类实现的方法声明(和/或属性)的集合。 标准做法是在运行时测试对象,看看它是否响应给定的选择器,然后再调用该方法,使用

-[NSObject respondsToSelector:]
。如果对象不响应选择器,则不会调用该方法。

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

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

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


17
投票

没有标准方法可以做到这一点,因为协议不应定义任何实现。

由于 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]];

在符合协议的类的初始化程序中,将添加所有默认方法。


4
投票

一个真正令人着迷的方法是使用运行时。在启动时,在程序执行的早期,执行以下操作:

  1. 枚举所有类,找到实现协议的类
  2. 检查类是否实现了方法
  3. 如果没有,请将默认实现添加到类中

无需那么麻烦就可以实现。


2
投票

我同意“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。


1
投票

正如 Ryan 提到的,协议没有默认实现,在超类中实现的另一个选择是实现一个“Handler”类型的类,该类可以包含在任何想要提供默认实现的类中,然后提供适当的方法调用默认处理程序实现。


1
投票

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.