Sto l'attuazione di un "Codice iniettore Class", che attraverso il metodo swizzling può darvi la possibilità di fare qualcosa di simile:Swizzling un metodo con argomenti variabili e inoltrare il messaggio - Bad Accesso
FLCodeInjector *injector = [FLCodeInjector injectorForClass:[self class]];
[injector injectCodeBeforeSelector:@selector(aSelector:) code:^{
NSLog(@"This code should be injected");
}];
aSelector
può essere un metodo con numero variabile di argomenti e tipo di ritorno variabile. Gli argomenti/e il tipo restituito possono essere oggetti o tipi primitivi.
In primo luogo, ho allegare il codice di injectCodeBeforeSelector:
per farvi capire quello che sto facendo (non rimosso parti interessanti del codice):
- (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);
}
Fondamentalmente, io uso class_addMethod di aggiungere il falso/swizzle metodo per la classe di destinazione, quindi fare il swizzle. L'implementazione del metodo è impostato su una funzione come questa:
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);
}
ora il problema: ho un EXC_BAD_INSTRUCTION (objc_msgSend_corrupt_cache_error()) nell'ultima riga. Il problema si verifica se inoltro gli argomenti va_list al selettore falso. Se cambio l'ultima linea di
return objc_msgSend(self, cmd, arguments);
nessun errore, ma ovviamente un infinito inizia ricorsione.
Ho cercato di:
- uso va_copy
- rimuovere lo swizzle prima di inviare il messaggio
ma senza risultati. Penso che il problema sia legato a questo fatto: va_list non è un semplice puntatore, può essere qualcosa di simile a un offset relativo all'indirizzo dello stack del metodo. Quindi, non posso chiamare objc_msgsend di una funzione (la funzione swizzled) con la lista arg di un'altra funzione (quella non swizzled).
Ho provato a cambiare approccio e copiare tutti gli argomenti in un NSInvocation, ma ho avuto altri problemi nella gestione del valore di ritorno dell'invocazione, e anche copiare gli argomenti uno per uno (gestendo tutti i diversi tipi) richiede un sacco di codice, quindi Ho preferito tornare a questo approccio, mi sembra più pulito (imho)
Hai qualche suggerimento? Grazie
grazie a entrambi, entrambi rispondono molto utile, contrassegnerò questo come corretto perché c'è una spiegazione più un esempio interessante. A causa di queste risposte, ho deciso di riprovare con NSInvocation, pubblicherò un'altra domanda al riguardo. Grazie ancora – LombaX
Se qualcuno è interessato, ecco un link per la prima versione della classe su github: https://github.com/lombax85/FLCodeInjector – LombaX