0

I have a class with the following method in one of my library's public headers. A user of my class will pass in the selector and class.

-(void)someMethodWithSelector(SEL)aSelector ofClass:(Class)clazz

At compile time, I don't know what the selector will look like, how many parameters will be passed, etc... but what I want is to be able to swizzle the passed selector at runtime, perform some extra logic, and call the original method afterwards.

I know how to swizzle class and instance methods, but I'm unsure how I would proceed given this scenario.

Has anyone had any experience dealing with a similar approach?

jscs
  • 63,694
  • 13
  • 151
  • 195
iOSAddicted
  • 389
  • 1
  • 17
  • Where is the selector coming from? How can you sanely add logic around a method you don't know anything about? – jscs Feb 03 '18 at 22:53
  • I don't understand the question. You are somehow getting a selector out of thin air and want to swizzle it? Are you doing `NSSelectorFromString` from a server call and trying to swizzle that? – Brandon Feb 03 '18 at 22:57
  • 1
    Related: [ObjC add functionality to every method of an object](https://stackoverflow.com/q/17116855), [Intercept all ObjC method calls](https://stackoverflow.com/q/45694335) Also see: [Is there a way to log every call to every method for a given class?](https://stackoverflow.com/q/26498519) and [probably some others](https://stackoverflow.com/search?q=%5Bobjc%5D+every+method+call) – jscs Feb 03 '18 at 23:00
  • Im asking a consumer of my class to pass me a selector(SEL) and the class(Class) it belongs. I want to swizzle it. The method is mentioned at the beginning of the post – iOSAddicted Feb 03 '18 at 23:01
  • So you know the selector at runtime!? – Amin Negm-Awad Feb 03 '18 at 23:06
  • Yes I do @AminNegm-Awad – iOSAddicted Feb 03 '18 at 23:07
  • So, if you know the selector and know how to swizzle methods, what is your Q? – Amin Negm-Awad Feb 03 '18 at 23:09
  • Read the question please. You know the selector at runtime, not compile time. The solution is not as simple as it seems. – iOSAddicted Feb 03 '18 at 23:13
  • 1
    Also see [IMP with unknown number of parameters](https://stackoverflow.com/q/18295758) and [Creating delegates on the spot with Blocks](https://stackoverflow.com/a/16113046/603977), because I'm pretty sure you're headed straight into the arms of libffi. Should be fun. – jscs Feb 03 '18 at 23:27
  • *I* changed the Q from runtime to compile time. However: if you know the selector and know how to swizzle methods, what is your Q? – Amin Negm-Awad Feb 04 '18 at 00:00
  • You seem to not understand the question or have an easy answer to it. If it is the second I challenge you to post the solution so we can upvote you :) – iOSAddicted Feb 04 '18 at 00:02
  • Just let me add something before you add a solution. In order to swizzle a selector you need a new selector, and its implementation, so the original method can be swizzled. You don’t know how that selector looks at compile time, how many arguments it has, what class it belongs, etc... – iOSAddicted Feb 04 '18 at 00:07
  • Amin is (verifiably) one of the ObjC experts that hangs around here; if he doesn't understand your question it may not be his fault. Either way, it's not going to help you to antagonize him. – jscs Feb 04 '18 at 02:36
  • @JoshCaswell I want to apologize to both you and Anim Negm-Awad if my question created confusion. English is not even my 2nd language and you are probably right, it might be my fault since multiple people in this same page had questions about my questions. I will spend more time from now on formulating my questions to try to avoid this type of confusion. – iOSAddicted Feb 04 '18 at 12:55
  • I don't think you need to apologize (and I understand what you're asking for); it's just that saying stuff like "read the question please" doesn't usually increase understanding. Anyways, you figured it out yourself, awesome! So probably enough said on this. :) – jscs Feb 04 '18 at 15:35

1 Answers1

0

MikeAsh was able to figure out this problem, so all the credit of this answer goes to him

@import Foundation;
@import ObjectiveC;
static NSMutableSet *swizzledClasses;
static NSMutableDictionary *swizzledBlocks; // Class -> SEL (as string) -> block
static IMP forwardingIMP;
static dispatch_once_t once;

void Swizzle(Class c, SEL sel, void (^block)(NSInvocation *)) {
    dispatch_once(&once, ^{
        swizzledClasses = [NSMutableSet set];
        swizzledBlocks = [NSMutableDictionary dictionary];
        forwardingIMP = class_getMethodImplementation([NSObject class], @selector(thisReallyShouldNotExistItWouldBeExtremelyWeirdIfItDid));
    });
    if(![swizzledClasses containsObject: c]) {
        SEL fwdSel = @selector(forwardInvocation:);
        Method m = class_getInstanceMethod(c, fwdSel);
        __block IMP orig;
        IMP imp = imp_implementationWithBlock(^(id self, NSInvocation *invocation) {
            NSString *selStr = NSStringFromSelector([invocation selector]);
            void (^block)(NSInvocation *) = swizzledBlocks[c][selStr];
            if(block != nil) {
                NSString *originalStr = [@"omniswizzle_" stringByAppendingString: selStr];
                [invocation setSelector: NSSelectorFromString(originalStr)];
                block(invocation);
            } else {
                ((void (*)(id, SEL, NSInvocation *))orig)(self, fwdSel, invocation);
            }
        });
        orig = method_setImplementation(m, imp);
        [swizzledClasses addObject: c];
    }
    NSMutableDictionary *classDict = swizzledBlocks[c];
    if(classDict == nil) {
        classDict = [NSMutableDictionary dictionary];
        swizzledBlocks[(id)c] = classDict;
    }
    classDict[NSStringFromSelector(sel)] = block;
    Method m = class_getInstanceMethod(c, sel);
    NSString *newSelStr = [@"omniswizzle_" stringByAppendingString: NSStringFromSelector(sel)];
    SEL newSel = NSSelectorFromString(newSelStr);
    class_addMethod(c, newSel, method_getImplementation(m), method_getTypeEncoding(m));
    method_setImplementation(m, forwardingIMP);
}

Here is how we would call the function:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Swizzle([NSBundle class], @selector(objectForInfoDictionaryKey:), ^(NSInvocation *inv) {
            NSLog(@"invocation is %@ - calling now", inv);
            [inv invoke];
            NSLog(@"after");
        });

        NSLog(@"%@", [[NSBundle bundleForClass: [NSString class]] objectForInfoDictionaryKey: (__bridge NSString *)kCFBundleVersionKey]);
    }
    return 0;
}
newacct
  • 119,665
  • 29
  • 163
  • 224
iOSAddicted
  • 389
  • 1
  • 17