I'm implementing a "Code Injector Class", that through method swizzling can give you the possibility to do something like this:
FLCodeInjector *injector = [FLCodeInjector injectorForClass:[self class]];
[injector injectCodeBeforeSelector:@selector(aSelector:) code:^{
NSLog(@"This code should be injected");
}];
aSelector
can be a method with variable number of arguments, and variable return type. Arguments / and return type can be objects or primitive type.
First, I attach the code of injectCodeBeforeSelector:
to let you understand what I'm doing (I removed not interesting parts of the code):
- (void)injectCodeBeforeSelector:(SEL)method code:(void (^)())completionBlock
{
NSString *selector = NSStringFromSelector(method);
[self.dictionaryOfBlocks setObject:completionBlock forKey:selector];
NSString *swizzleSelector = [NSString stringWithFormat:@"SWZ%@", selector];
// add a new method to the swizzled class
Method origMethod = class_getInstanceMethod(self.mainClass, NSSelectorFromString(selector));
const char *encoding = method_getTypeEncoding(origMethod);
[self addSelector:NSSelectorFromString(swizzleSelector) toClass:self.mainClass methodTypeEncoding:encoding];
SwizzleMe(self.mainClass, NSSelectorFromString(selector), NSSelectorFromString(swizzleSelector));
}
-(void)addSelector:(SEL)selector toClass:(Class)aClass methodTypeEncoding:(const char *)encoding
{
class_addMethod(aClass,
selector,
(IMP)genericFunction, encoding);
}
Basically, I use class_addMethod to add the fake/swizzle method to the destination class, then do the swizzle. The implementation of the method is set to a function like this:
id genericFunction(id self, SEL cmd, ...) {
// call the block to inject
...
// now forward the message to the right method, since method are swizzled
// I need to forward to the "fake" selector SWZxxx
NSString *actualSelector = NSStringFromSelector(cmd);
NSString *newSelector = [NSString stringWithFormat:@"SWZ%@", actualSelector];
SEL finalSelector = NSSelectorFromString(newSelector);
// forward the argument list
va_list arguments;
va_start ( arguments, cmd );
return objc_msgSend(self, finalSelector, arguments);
}
now the problem: I have an EXC_BAD_INSTRUCTION ( objc_msgSend_corrupt_cache_error ()) on the last line. The problem happens if i forward the va_list arguments to the fake selector. If I change the last line to
return objc_msgSend(self, cmd, arguments);
no error, but obviously an infinite recursion starts.
I've tried to:
- use va_copy
- remove the swizzle before sending the message
but no results. I think that the problem is related to this fact: va_list is not a simple pointer, it can be something similar to an offset relative to the stack address of the method. So, I can't call objc_msgsend of a function (the swizzled function) with the arg list of another function (the non-swizzled one).
I tried to change approach and copy all arguments in an NSInvocation, but I had other problems managing the return value of the invocation, and even copy arguments one by one (managing all different types) requires a lot of code, so I preferred to return to this approach, that seems cleaner to me (imho)
Do you have any suggestion? Thanks