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

发布于 2024-10-05 16:47:50 字数 117 浏览 5 评论 0原文

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

没有伤那来痛 2024-10-12 16:47:50

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

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

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

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

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.

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.

烙印 2024-10-12 16:47:50

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

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

符合协议的类的初始值设定项,所有默认方法都将是额外。

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.

嘿咻 2024-10-12 16:47:50

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

  1. 枚举所有类,查找实现协议的类
  2. 检查该类是否实现了某个方法
  3. 如果没有,则向该类添加默认实现

无需实现即可实现这么麻烦。

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.

完美的未来在梦里 2024-10-12 16:47:50

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

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.

The following helper functions worked for me

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

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.

画▽骨i 2024-10-12 16:47:50

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

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.

书间行客 2024-10-12 16:47:50

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

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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文