3

In my class, I have a reference on an UIViewController and want to implement a delegate on this ViewController during runtime. The delegate has only one method (with two parameters) and when the delegate-method on the ViewController is invoked, my class should handle the call.

I am quite sure this is possible with some kind of method swizzling, etc. but I don't know how to accomplish this.

danielbuechele
  • 3,104
  • 2
  • 18
  • 29
  • possible duplicate of [Method Swizzle on iPhone device](http://stackoverflow.com/questions/1637604/method-swizzle-on-iphone-device) – i_am_jorf May 23 '14 at 00:04
  • 2
    Why do you have to do this switch at runtime? Unless this view controller isn't your own. It sounds like you just make a call to your class's method from the UIViewController's delegate method. Maybe more information is needed. – rob5408 May 23 '14 at 00:47
  • @rob5408 sorry, should have said this. I am working on a framework and the ViewController isn't my own. I just get passed the reference from outside. – danielbuechele May 23 '14 at 08:46
  • 2
    @jeffamaphone Hmmm. I don't think this question is a duplicate of the one about swizzling. – Jasper Blues May 24 '14 at 01:46

2 Answers2

3

What you want is possible, but it's not method swizzling, since you don't want to switch to methods but add a new one. It can be done, thanks to Objective-C's dynamic nature, but it's still a dirty hack so also file a feature request with the library vendor.

What you want is class_addMethod() and a C function with the actual implementation for that. One more thing, Objective-C methods are C methods, but with two implicit parameters, self and _cmd, which have to keep in mind (both when creating your C method and when telling class_addMethod your methods signature. And here is an SSCE of how to pull something like that off:

#import <Foundation/Foundation.h>
#import <objc/runtime.h> // Required for class_addMethod()

@interface MyClass : NSObject
@end

@implementation MyClass
@end


@protocol MyProtocol <NSObject>
- (void)printString:(NSString *)string;
@end




// Note the method signature containing the
// two implicit parameters self and _cmd!
void MyClassPrinStringIMP(id self, SEL _cmd, NSString *string)
{
    NSLog(@"Hi I'm %@:%s and this is the string: %@", self, sel_getName(_cmd), string);
}


void PimpMyClass()
{
    // The last argument is the signature. First character is the return type, in our case void
    // Then comes self and _cmd, followed by the NSString. You can use @encode() to find out how your
    // type is encoded. Best is to build this string at runtime, since the encoding can change with architectures
    class_addMethod([MyClass class], @selector(printString:), (IMP)MyClassPrinStringIMP, "v@:@");
}



int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
        PimpMyClass();

        id foo = [[MyClass alloc] init]; // id, to silence the compiler!
        [foo printString:@"Hello World"];
    }

    return 0;
}

Example output:

Hi I'm <MyClass: 0x100101810>:printString: and this is the string: Hello World

Edit: Something that you may find is that the passed object is checked at runtime wether it conforms to a protocol or not using conformsToProtocol:. Since this code just adds the method implementation, it would still fail, but you can tell the runtime that you totally do implement that protocol with this one function call:

class_addProtocol([MyClass class], @protocol(MyProtocol));

Alternative: proxies

Objective-Cs dynamism and message forwarding is already praised by @JasperBlues, however, there is one particular class in Objective-C that is designed to do just that: NSProxy. It is designed to intercept sent messages and dispatching them dynamically to the relevant target, and does use the high-level NSInvocation approach. If you can pass a proxied object in some way as the delegate (depending on what your code allows for and what not), creating a NSProxy subclass might be the cleanest way to go.

However, note though that you then end up with a shim object that wraps over your other object, which comes with its own bag of pain and will break when you try to directly access variables via -> syntax. It's not a perfectly invisible proxy, but good enough for most cases.

JustSid
  • 25,168
  • 7
  • 79
  • 97
2

Firstly, some comments indicate that what you're asking is instantly "a bad thing to do" or a "dirty hack". I disagree here. Most modern Object Oriented languages support these features, and they are used to good effect by numerous system-level frameworks. Of course it is human-nature to perhaps use these dynamic features where they're not really required (for fun or practice), even when a simpler approach would work fine. Beware of this.

Objective-C is admirable in that its somewhat of a legacy language and close to the "bare metal", and yet features a surprising level of dynamism, making it relatively easy to support these requirements without any external libraries or frameworks.

Besides using the class_addMethod guide that another answer correctly indicates, some other approaches are:


Message Forwarding: (recommended)

All NSObject sub-classes have the ability to forward a method that they're not able to respond to, to another target object. This is similar to the lower-level concept of trampolines. Apple publishes a guide on using this approach.

The advantages of using forward invocation is that it uses the NSInvocation level of abstraction, instead of directly calling the C ObjC runtime API. This abstracts the following details away:

  • Structs and primitives will be box/unboxed automatically
  • Dispatching to methods with a dynamic/unknown number of arguments becomes easy. Until arm64, this could be done using va_args, however on arm64 va_args can be copied directly to registers, and not popped off the stack.


Resolve Instance Method:

Instance methods are created by by registering a C function as the implementation to respond to a given message. This can be done neatly with blocks, using IMP_ImplementationWithBlock:

+ (BOOL)resolveInstanceMethod:(SEL)sel
{

        IMP imp = imp_implementationWithBlock((__bridge id) objc_unretainedPointer(
            ^(id me, BOOL firstParam, NSString* secondParam)
        {
            //Implementation goes in here
            return something; //something of type 'id'

        }));
        class_addMethod(self, sel, imp, "@@:");
        return YES;
    }
    return NO;
}


Using libffi:

Libffi can also do this kind of thing, though it should not be necessary if you're using pure Objective-C, as the runtime already has these features baked in.

Jasper Blues
  • 28,258
  • 22
  • 102
  • 185
  • 1
    Picking up on your forwarding suggestion, I expanded my answer to also cover `NSProxy`. OP should have enough material now to pick the right tool for his use case :) – JustSid May 24 '14 at 08:47