在运行时调配特定 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;
}
我的图书馆 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;
}