在运行时调配特定 class 的未知选择器

Swizzle an unknown selector of a specific class at runtime

我的图书馆 public headers 中有一个 class 使用以下方法。我的 class 的用户将传入选择器和 class.

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

在编译时,我不知道选择器会是什么样子,会传递多少参数等等...但我想要的是能够在运行时调配传递的选择器,执行一些额外的逻辑,然后调用原来的方法。

我知道如何调整 class 和实例方法,但我不确定在这种情况下我将如何进行。

有没有人有处理类似方法的经验?

MikeAsh 能够解决这个问题,所以this answer的功劳全归他

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

下面是我们调用函数的方式:

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