ich einen „Code-Injektor Klasse“ bin Umsetzung, dass durch Verfahren Swizzling kann Ihnen die Möglichkeit, so etwas zu tun:Swizzling ein Verfahren mit variablen Argumenten und die Nachricht weiterleiten - Bad Zugang
FLCodeInjector *injector = [FLCodeInjector injectorForClass:[self class]];
[injector injectCodeBeforeSelector:@selector(aSelector:) code:^{
NSLog(@"This code should be injected");
}];
aSelector
kann eine Methode mit einer variablen Anzahl von Argumenten und einem variablen Rückgabetyp sein. Argumente/und Rückgabetyp können Objekte oder primitive Typen sein.
Zuerst lege ich den Code von injectCodeBeforeSelector:
, damit Sie verstehen, was ich tue (ich nicht interessant Teile des Codes entfernt):
- (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);
}
Grundsätzlich verwende ich class_addMethod die gefälschte hinzufügen/swizzle Methode zur Zielklasse, dann swizzle. Die Durchführung des Verfahrens ist mit einer Funktion wie folgt festgelegt:
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);
}
jetzt das Problem: Ich habe eine EXC_BAD_INSTRUCTION (objc_msgSend_corrupt_cache_error()) in der letzten Zeile. Das Problem tritt auf, wenn ich die Argumente va_list an den falschen Selektor weiterleiten. Wenn ich ändern die letzte Zeile zu
return objc_msgSend(self, cmd, arguments);
kein Fehler, aber offensichtlich eine unendliche Rekursion beginnt.
Ich habe versucht, zu:
- Verwendung va_copy
- die swizzle entfernen, bevor die Nachricht
aber keine Ergebnisse zu senden. Ich denke, dass das Problem mit dieser Tatsache verbunden ist: va_list ist kein einfacher Zeiger, es kann etwas sein, das einem Offset relativ zur Stapeladresse der Methode ähnlich ist. Also kann ich objc_msgsend einer Funktion (die swizzled-Funktion) nicht mit der Arg-Liste einer anderen Funktion aufrufen (die nicht swizzed).
Ich versuchte Ansatz zu ändern und alle Argumente in einer NSInvocation zu kopieren, aber ich hatte andere Probleme, den Rückgabewert des Aufrufs zu verwalten, und sogar Argumente nacheinander kopieren (alle unterschiedlichen Typen verwalten) erfordert viel Code Ich zog es vor, zu diesem Ansatz zurückzukehren, der mir sauberer scheint (imho)
Haben Sie einen Vorschlag? Danke
danke beide, beide antworten sehr nützlich, ich werde diese als richtig markieren, weil es eine Erklärung plus ein interessantes Beispiel gibt. Aufgrund dieser Antworten habe ich mich dazu entschieden, mit NSInvocation erneut zu versuchen. Ich werde eine weitere Frage dazu stellen. Nochmals vielen Dank – LombaX
Wenn jemand interessiert ist, hier ist ein Link für die erste Version der Klasse auf GitHub: https://github.com/lombax85/FLCodeInjector – LombaX