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

时间:2022-09-07 08:30:14

I'd like to specify an Objective-C protocol with an optional routine. When the routine is not implemented by a class conforming to the protocol I'd like to use a default implementation in its place. Is there a place in the protocol itself where I can define this default implementation? If not, what is the best practice to reduce copying and pasting this default implementation all over the place?

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

6 个解决方案

#1


13  

Objective-C protocols have no affordance for default implementations. They are purely collections of method declarations that can be implemented by other classes. The standard practice in Objective-C is to test an object at runtime to see if it responds to the given selector before calling that method on it, using -[NSObject respondsToSelector:]. If e object does not respond to the given selector, the method isn't called.

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

One way you could achieve the result you're looking for would be to define a method encapsulating the default behavior you're looking for in the calling class, and call that method if the object doesn't pass the test.

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

Another approach would be to make the method be required in the protocol, and provide default implementations in the superclasses of any classes wherein you may not want to provide a specific implementation.

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

There are probably other options as well, but generally speaking there isn't a particular standard practice in Objective-C, except perhaps to just not call the given method if it hasn't been implement by the object, per my first paragraph, above.

也可能有其他的选项,但是一般来说Objective-C中没有特定的标准实践,除非如果给定的方法没有被对象实现,比如我上面的第一个段落。

#2


14  

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).

由于Objective-C附带了一个整洁的运行时,如果您真的认为需要这样做的话,当然可以添加这样的行为(继承也不可能实现同样的行为)。

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

假设您声明了MyProtocol,然后在协议声明下的.h文件中添加一个名称相同的接口:

@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):

并创建一个相应的实现文件(在这里使用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
}

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.

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

#3


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. 枚举所有类,找到实现该协议的类
  3. Check if the class implements a method
  4. 检查类是否实现了一个方法
  5. If not, add to the class the default implementation
  6. 如果不是,则向类添加默认实现

It can be achieved without that much trouble.

不需要那么多麻烦就可以实现。

#4


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.

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

#5


1  

I ended up creating a macro that has a default implementation of the method.

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

I've defined it in the protocol's header file, and then it's just a one-liner in each implementation.

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

This way, I do not have to change the implementation several places, and it's done on compile time, so no run-time magic is necessary.

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

#6


1  

I agree with "w.m." A very nice solution is to put all the default implementations into an interface (with the same name as the protocol). In the "+initialize" method of any subclass it can simply copy any unimplemented methods from the default interface into itself.

我同意“w.m。”一个很好的解决方案是将所有的默认实现放在一个接口中(与协议的名称相同)。在任何子类的“+initialize”方法中,它可以将任何未实现的方法从默认接口复制到自身中。

The following helper functions worked for me

下面的helper函数为我工作。

#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);
}

Then you call it in your class initializer such as...

然后在类初始化器中调用它,比如…

@interface Foobar : NSObject<MyProtocol>  
@end

@implementation Foobar
+(void)initialize
{
    // Copy methods from the default
    copyMissingMethods([MyProtocol class], self);
}
@end

Xcode will give you warnings about Foobar missing methods, but you can ignore them.

Xcode会给你关于Foobar丢失方法的警告,但是你可以忽略它们。

This technique only copies methods, not ivars. If the methods are accessing data members that do not exist, you could get strange bugs. You must ensure that the data is compatible with the code. It is as if you did a reinterpret_cast from Foobar to MyProtocol.

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

#1


13  

Objective-C protocols have no affordance for default implementations. They are purely collections of method declarations that can be implemented by other classes. The standard practice in Objective-C is to test an object at runtime to see if it responds to the given selector before calling that method on it, using -[NSObject respondsToSelector:]. If e object does not respond to the given selector, the method isn't called.

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

One way you could achieve the result you're looking for would be to define a method encapsulating the default behavior you're looking for in the calling class, and call that method if the object doesn't pass the test.

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

Another approach would be to make the method be required in the protocol, and provide default implementations in the superclasses of any classes wherein you may not want to provide a specific implementation.

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

There are probably other options as well, but generally speaking there isn't a particular standard practice in Objective-C, except perhaps to just not call the given method if it hasn't been implement by the object, per my first paragraph, above.

也可能有其他的选项,但是一般来说Objective-C中没有特定的标准实践,除非如果给定的方法没有被对象实现,比如我上面的第一个段落。

#2


14  

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).

由于Objective-C附带了一个整洁的运行时,如果您真的认为需要这样做的话,当然可以添加这样的行为(继承也不可能实现同样的行为)。

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

假设您声明了MyProtocol,然后在协议声明下的.h文件中添加一个名称相同的接口:

@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):

并创建一个相应的实现文件(在这里使用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
}

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.

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

#3


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. 枚举所有类,找到实现该协议的类
  3. Check if the class implements a method
  4. 检查类是否实现了一个方法
  5. If not, add to the class the default implementation
  6. 如果不是,则向类添加默认实现

It can be achieved without that much trouble.

不需要那么多麻烦就可以实现。

#4


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.

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

#5


1  

I ended up creating a macro that has a default implementation of the method.

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

I've defined it in the protocol's header file, and then it's just a one-liner in each implementation.

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

This way, I do not have to change the implementation several places, and it's done on compile time, so no run-time magic is necessary.

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

#6


1  

I agree with "w.m." A very nice solution is to put all the default implementations into an interface (with the same name as the protocol). In the "+initialize" method of any subclass it can simply copy any unimplemented methods from the default interface into itself.

我同意“w.m。”一个很好的解决方案是将所有的默认实现放在一个接口中(与协议的名称相同)。在任何子类的“+initialize”方法中,它可以将任何未实现的方法从默认接口复制到自身中。

The following helper functions worked for me

下面的helper函数为我工作。

#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);
}

Then you call it in your class initializer such as...

然后在类初始化器中调用它,比如…

@interface Foobar : NSObject<MyProtocol>  
@end

@implementation Foobar
+(void)initialize
{
    // Copy methods from the default
    copyMissingMethods([MyProtocol class], self);
}
@end

Xcode will give you warnings about Foobar missing methods, but you can ignore them.

Xcode会给你关于Foobar丢失方法的警告,但是你可以忽略它们。

This technique only copies methods, not ivars. If the methods are accessing data members that do not exist, you could get strange bugs. You must ensure that the data is compatible with the code. It is as if you did a reinterpret_cast from Foobar to MyProtocol.

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