How do I provide a default implementation for an Objective-C protocol?
M

6

25

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?

Michelinemichell answered 2/12, 2010 at 0:45 Comment(1)
This post offers an elegant, novel solution: use a category on NSObject. #19329809Croak
C
19

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.

Corporation answered 2/12, 2010 at 1:6 Comment(0)
G
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.

Governess answered 2/12, 2010 at 1:48 Comment(2)
At one point I tried with github.com/jspahrsummers/libextobjc/blob/master/extobjc/… but couldn't get it working, and the concrete protocols part seemed unmaintained. So that made me unsure whether I was going in the right direction. But I'll look into MAObjCRuntime or the standard runtime functions. Thanks for the pointer!Oleneolenka
I have provided an implementation below that does not use third party libraries. see #4331156Croak
E
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.

Equimolecular answered 2/12, 2010 at 1:47 Comment(4)
How dare you call this hackish ;)Governess
I've actually written a full, public domain module to do just this. It's not as hackish as it sounds, as it uses completely supported and documented functionality, and has been thoroughly tested in production code. code.google.com/p/libextobjc/source/browse/extobjc/Modules/…Nerti
OK I replace "hackish" with "fascinating". Will that satisfy you all?Equimolecular
Maybe it's not hackish, but it's not standard practice, and it'll definitely be confusing to debug.Corporation
C
2

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.

Croak answered 14/4, 2014 at 17:44 Comment(0)
T
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.

Touchmenot answered 2/12, 2010 at 1:49 Comment(0)
O
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.

Oleneolenka answered 25/2, 2014 at 13:20 Comment(2)
A macro?! boo, hiss. The best answer is from "w.m." You should create a class that contains all the default implementations. Also provide a utility (such as addDefaultImplementationForClass) that copies the default methods (using class_addMethod). You call the utility in each class initialize. There is nothing hackish about this when you consider that Objective-C is a descendant of LISP.Croak
I agree that macros are not a good solution, and that taking advantage of the run-time is idiomatic ObjC. I actually already ditched the macros, and currently I'm using a helper object that my protocol dictates. But that goes agains the principle of least knowledge, so I think I'll consider w.m.'s solution.Oleneolenka

© 2022 - 2024 — McMap. All rights reserved.