2009-10-24 14 views
18

Posso intercettare una chiamata di metodo in Objective-C? Come?Chiamata di metodo di intercettazione in Objective-C

Edit: risposta Mark Powell s' mi ha dato una soluzione parziale, il metodo -forwardInvocation. Ma la documentazione afferma che -forwardInvocation viene chiamato solo quando un oggetto viene inviato un messaggio per il quale non ha un metodo corrispondente. Mi piacerebbe che un metodo venisse chiamato in tutte le circostanze, anche se il ricevitore ha quel selettore.

risposta

22

L'operazione viene eseguita girando la chiamata al metodo.Supponendo che si desidera afferrare tutte le emissioni di NSTableView:

static IMP gOriginalRelease = nil; 
static void newNSTableViewRelease(id self, SEL releaseSelector, ...) { 
    NSLog(@"Release called on an NSTableView"); 
    gOriginalRelease(self, releaseSelector); 
} 


    //Then somewhere do this: 
    gOriginalRelease = class_replaceMethod([NSTableView class], @selector(release), newNSTableViewRelease, "[email protected]:"); 

È possibile ottenere ulteriori informazioni in C runtime Obiettivo documentation.

+0

Probabilmente intendevi @selector (release), non @selector (dealloc) –

+0

Sì, volevo dire @selector (release). Questo mi piace e lo incollo da un codice che ha ingannato dealloc, ma non volevo mostrarlo, ad esempio, perché ci sono alcuni problemi particolari nel farlo. Fisso. –

+0

@LouisGerbarg Potete per favore pubblicare un link alla vostra fonte originale che ha rilasciato il rilascio? – funroll

-1

Per fare qualcosa quando viene chiamato un metodo, è possibile provare un approccio basato sugli eventi. Quindi, quando viene chiamato il metodo, trasmette un evento, che viene rilevato da qualsiasi ascoltatore. Non sono bravo con l'obiettivo C, ma ho appena scoperto qualcosa di simile usando NSNotificationCenter a Cocoa.

Ma se per "intercettare" intendi "stop", allora forse hai bisogno di più logica per decidere se il metodo debba essere chiamato a tutti.

+2

Penso che si riferisca alle tecniche di metaprogrammazione. – Geo

1

Forse si desidera il metodo-forwardInvocation. Questo ti permette di catturare un messaggio, retargetarlo e poi inviarlo di nuovo.

+2

La documentazione afferma che * -forwardInvocation * viene chiamato solo quando un oggetto viene inviato un messaggio per il quale non ha un metodo corrispondente. Mi piacerebbe che un metodo venisse chiamato in tutte le circostanze, anche se il ricevitore ha quel selettore. – luvieere

+1

Questo commento ha molte più informazioni rispetto alla tua domanda originale ...;) – MarkPowell

+0

I'll edit, grazie per aver segnalato. – luvieere

0

Una chiamata di metodo, no. Un messaggio invia, sì, ma dovrai essere molto più descrittivo se vuoi una buona risposta su come.

+2

Il termine per una chiamata di metodo in Obj-C è "un messaggio di invio" in modo che siano uno e loro stessi, al contrario di una chiamata di funzione che - si è corretto - non può essere intercettata dinamicamente. –

1

È possibile scambiare la chiamata al metodo con una propria, che fa tutto ciò che si vuole fare su "intercettazione" e chiama fino all'implementazione originale. Swizzling è fatto con class_replaceMethod().

15

metodo intercettare le chiamate in Objective-C (asuming è un Objective-C, non una chiamata C) è fatto con una tecnica chiamata metodo swizzling.

È possibile trovare un'introduzione su come implementare tale here. Per un esempio su come il metodo di swizzling è implementato in un progetto reale, controlla OCMock (un framework di isolamento per Objective-C).

11

invio di un messaggio in Objective-C si traduce in una chiamata di funzione objc_msgSend(receiver, selector, arguments) o una delle sue varianti, objc_msgSendSuperobjc_msgSend_stret, objc_msgSendSuper_stret.

Se fosse possibile modificare l'implementazione di queste funzioni, potremmo intercettare qualsiasi messaggio. Sfortunatamente, objc_msgSend fa parte del runtime Objective-C e non può essere sovrascritto.

Su Google ho trovato un articolo su Google Libri: A Reflective Architecture for Process Control Applications by Charlotte Pii Lunau. Il documento introduce un hack reindirizzando il puntatore di una classe isa a un'istanza di una classe MetaObject personalizzata. I messaggi destinati all'oggetto modificato vengono quindi inviati all'istanza MetaObject. Poiché la classe MetaObject non ha metodi propri, può quindi rispondere all'invocazione in avanti inoltrando il messaggio all'oggetto modificato.

Il documento non include i bit interessanti del codice sorgente e non ho idea se tale approccio avrebbe effetti collaterali in Cocoa. Ma potrebbe essere interessante provarlo.

2

creare una sottoclasse di NSProxy e attuare -forwardInvocation: e -methodSignatureForSelector: (o -forwardingTargetForSelector:, se siete semplicemente dirigere mai ad un secondo oggetto, invece di giocherellare con il metodo da soli).

NSProxy è una classe progettata per l'implementazione di -forwardInvocation: su. Ha alcuni metodi, ma soprattutto non vuoi che vengano catturati. Ad esempio, l'intercettazione dei metodi di conteggio dei riferimenti impedirebbe l'allocazione del proxy ad eccezione della garbage collection. Ma se ci sono metodi specifici su NSProxy che è assolutamente necessario inoltrare, è possibile sovrascrivere quel metodo in modo specifico e chiamare manualmente . Si noti che semplicemente perché un metodo è elencato nella documentazione NSProxy non significa che NSProxy lo implementa, ma semplicemente che è previsto che tutti gli oggetti proxy lo abbiano.

Se ciò non funziona, fornire ulteriori dettagli sulla situazione.

5

Se si desidera registrare le mandate dei messaggi dal proprio codice applicazione, the -forwardingTargetForSelector: tip fa parte della soluzione.
Avvolgete il vostro oggetto:

@interface Interceptor : NSObject 
@property (nonatomic, retain) id interceptedTarget; 
@end 

@implementation Interceptor 
@synthesize interceptedTarget=_interceptedTarget; 

- (void)dealloc { 
    [_interceptedTarget release]; 
    [super dealloc]; 
} 

- (id)forwardingTargetForSelector:(SEL)aSelector { 
    NSLog(@"Intercepting %@", NSStringFromSelector(aSelector)); 
    return self.interceptedTarget; 
} 

@end 

Ora fare qualcosa di simile:

Interceptor *i = [[[Interceptor alloc] init] autorelease]; 
NSFetchedResultsController *controller = [self setupFetchedResultsController]; 
i.interceptedTarget = controller; 
controller = (NSFetchedResultsController *)i; 

e si avrà un log di messaggio manda. Nota, le mandate inviate dall'interno dell'oggetto intercettato non verranno intercettate, poiché verranno inviate utilizzando il puntatore "auto" dell'oggetto originale.

3

Se solo si desidera registrare i messaggi chiamati dall'esterno (di solito chiamato da delegati, per vedere quale tipo di messaggi, quando, ecc), è possibile ignorare respondsToSelector in questo modo:

- (BOOL)respondsToSelector:(SEL)aSelector { 
NSLog(@"respondsToSelector called for '%@'", NSStringFromSelector(aSelector)); 

// look up, if a method is implemented 
if([[self class] instancesRespondToSelector:aSelector]) return YES; 

return NO; 

}

Problemi correlati