【问题标题】:How do I provide a default implementation for an Objective-C protocol?如何为 Objective-C 协议提供默认实现?
【发布时间】:2011-05-18 20:32:33
【问题描述】:

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

【问题讨论】:

标签: objective-c protocols standards-compliance default-implementation overhead-minimization


【解决方案1】:

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

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

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

可能还有其他选项,但一般来说,Objective-C 中没有特定的标准做法,除了可能只是不调用给定的方法,如果它还没有被对象实现,根据我的第一个上面的段落。

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    【解决方案3】:

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

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

    不用那么麻烦就可以实现。

    【讨论】:

    • 你怎么敢说这是骇人听闻的 ;)
    • 我实际上已经编写了一个完整的公共域模块来执行此操作。它并不像听起来那么骇人听闻,因为它使用完全支持和记录的功能,并且已经在生产代码中进行了彻底的测试。 code.google.com/p/libextobjc/source/browse/extobjc/Modules/…
    • 好的,我将“hackish”替换为“着迷”。这会让大家满意吗?
    • 也许这不是hackish,但它不是标准做法,而且调试起来肯定会令人困惑。
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

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

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

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

        【讨论】:

        • 宏?!嘘,嘶嘶声。最佳答案来自“w.m”。您应该创建一个包含所有默认实现的类。还提供复制默认方法(使用 class_addMethod)的实用程序(例如 addDefaultImplementationForClass)。您在每个类中调用实用程序初始化。当你认为 Objective-C 是 LISP 的后代时,这并没有什么骇人听闻的。
        • 我同意宏不是一个好的解决方案,并且利用运行时是惯用的 ObjC。我实际上已经放弃了宏,目前我正在使用我的协议规定的辅助对象。但这违背了最少知识的原则,所以我想我会考虑 w.m. 的解决方案。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-15
        • 2014-09-23
        • 1970-01-01
        • 2015-01-01
        • 2011-09-15
        相关资源
        最近更新 更多