0

ライブラリのパブリック ヘッダーの 1 つに、次のメソッドを持つクラスがあります。私のクラスのユーザーは、セレクターとクラスを渡します。

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

コンパイル時に、セレクターがどのように見えるか、渡されるパラメーターの数などはわかりません...しかし、実行時に渡されたセレクターをスウィズルし、追加のロジックを実行し、呼び出すことができるようにする必要があります後は元の方法。

クラスとインスタンスのメソッドを入れ替える方法は知っていますが、このシナリオをどのように進めるかはわかりません。

誰かが同様のアプローチを扱った経験がありますか?

4

1 に答える 1

0

MikeAshはこの問題を解決できたので、この回答 の功績はすべて彼に帰する

@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;
}
于 2018-02-04T13:14:08.513 に答える