Jestem wykonawczych „kod wtryskiwacza Class”, który dzięki metodzie swizzling może dać Ci możliwość zrobienia czegoś takiego:Swizzling metody ze zmiennymi argumentami i przekazać wiadomość - Bad dostęp
FLCodeInjector *injector = [FLCodeInjector injectorForClass:[self class]];
[injector injectCodeBeforeSelector:@selector(aSelector:) code:^{
NSLog(@"This code should be injected");
}];
aSelector
może być metodą ze zmienną liczbą argumentów i zmiennym typem zwracanym. Argumenty/i typ zwracany mogą być obiektami lub typem pierwotnym.
pierwsze, załączam kod injectCodeBeforeSelector:
pozwolić Ci zrozumieć, co robię (nie usuwa interesujących części kodu):
- (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);
}
Zasadniczo używam class_addMethod dodać fałszywy/swizzle do klasy docelowej, a następnie wykonaj zawroty. Wdrożenie metody jest ustawiony na funkcję tak:
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);
}
Teraz problem: Mam EXC_BAD_INSTRUCTION (objc_msgSend_corrupt_cache_error()) na ostatniej linii. Problem występuje, jeśli przekazuję argumenty va_list do fałszywego selektora. Jeśli zmienię ostatnią linię do
return objc_msgSend(self, cmd, arguments);
żaden błąd, ale oczywiście nieskończona rozpoczyna rekursji.
Próbowałem:
- użytku va_copy
- usunąć swizzle przed wysłaniem wiadomości
ale bez rezultatów. Myślę, że problem jest związany z tym faktem: va_list nie jest prostym wskaźnikiem, może być czymś podobnym do offsetu względem adresu stosu metody. Tak więc, nie mogę nazwać obiektu objc_msgsend (funkcja swizzled) z listą argumentów innej funkcji (ta, która nie jest przekierowana).
Próbowałem zmienić podejście i skopiować wszystkie argumenty w NSInvocation, ale miałem inne problemy z zarządzaniem zwracaną wartością wywołania, a nawet kopiowanie argumentów jedna po drugiej (zarządzanie wszystkimi różnymi typami) wymaga dużej ilości kodu, więc Wolałem wrócić do tego podejścia, które wydaje mi się bardziej czyste (imho)
Czy masz jakieś sugestie? Dzięki
dziękuję wam obojgu, obaj odpowiadają bardzo pożytecznym, ja będę oznaczyć ten jeden poprawny ponieważ jest objaśnienie plus ciekawy przykład. Z powodu tych odpowiedzi zdecydowałem się spróbować ponownie przy pomocy NSInvocation, opublikuję kolejne pytanie na ten temat. Jeszcze raz dziękuję – LombaX
Jeśli ktoś jest zainteresowany, oto link do pierwszej wersji klasy na github: https://github.com/lombax85/FLCodeInjector – LombaX