Swizzling метод с переменными аргументами и переслать сообщение - Bad Access
Я реализую «Code Injector Class», который через метод swizzling может дать вам возможность сделать что-то вроде этого:
FLCodeInjector *injector = [FLCodeInjector injectorForClass:[self class]];
[injector injectCodeBeforeSelector:@selector(aSelector:) code:^{
NSLog(@"This code should be injected");
}];
aSelector
может быть методом с переменным числом аргументов и типом возвращаемой переменной. Аргументы / и возвращаемый тип могут быть объектами или примитивным типом.
Сначала я прикрепляю кодinjectCodeBeforeSelector:
чтобы вы поняли, что я делаю (я удалил не интересные части кода):
- (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);
}
По сути, я использую class_addMethod, чтобы добавить метод fake / swizzle к классу назначения, а затем выполнить swizzle. Реализация метода устанавливается на функцию, подобную этой:
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);
}
Теперь проблема: у меня есть EXC_BAD_INSTRUCTION (objc_msgSend_corrupt_cache_error ()) в последней строке. Проблема возникает, если я пересылаю аргументы va_listк поддельному селектору, Если я изменю последнюю строку на
return objc_msgSend(self, cmd, arguments);
без ошибок, но, очевидно, начинается бесконечная рекурсия.
Я пытался:
использовать va_copyудалите алкоголь перед отправкой сообщенияно нет результатов. Я думаю, что проблема связана с этим фактом: va_list не является простым указателем, он может быть чем-то похожим на смещение относительно адреса стека метода. Таким образом, я не могу вызвать objc_msgsend функции (swizzled function) со списком arg другой функции (не swizzled).
Я попытался изменить подход и скопировать все аргументы в NSInvocation, но у меня были другие проблемы с управлением возвращаемым значением вызова, и даже копирование аргументов один за другим (управление всеми различными типами) требует большого количества кода, поэтому я предпочел вернуться для этого подхода, который кажется мне чище (imho)
У вас есть какие-либо предложения? Спасибо