Я реализую «Класс инжектора кода», который с помощью переключения методов может дать вам возможность сделать что-то вроде этого:
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
- удалите swizzle перед отправкой сообщения
но без результатов. Думаю, проблема связана с этим фактом: va_list - это не простой указатель, это может быть что-то вроде смещения относительно адреса стека метода. Итак, я не могу вызвать objc_msgsend функции (swizzled-функцию) со списком arg другой функции (non-swizzled).
Я попытался изменить подход и скопировать все аргументы в NSInvocation, но у меня были другие проблемы с управлением возвращаемым значением вызова, и даже для копирования аргументов один за другим (управление всеми разными типами) требуется много кода, поэтому я предпочел вернуть в этом подходе, который мне кажется чище (имхо)
Есть ли у вас предложения? Спасибо