2009-10-24 4 views
18

Kann ich einen Methodenaufruf in Objective-C abfangen? Wie?Intercept-Methodenaufruf in Objective-C

Edit: Mark Powell ‚s Antwort gab mir eine Teillösung, die -forwardInvocation Methode. Aber die Dokumentation besagt, dass -forwardInvocation nur aufgerufen wird, wenn ein Objekt eine Nachricht gesendet wird, für die es keine entsprechende Methode hat. Ich möchte, dass eine Methode unter allen Umständen aufgerufen wird, selbst wenn der Empfänger diesen Selektor hat.

Antwort

22

Sie tun es, indem Sie den Methodenaufruf swizzlen.Angenommen, Sie alle Meldungen zu NSTableView greifen wollen:

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]:"); 

Sie können mehr Details in der Objective-C-Laufzeit documentation bekommen.

+0

Wahrscheinlich meintest du @selector (release), nicht @selector (dealloc) –

+0

Ja, ich meine @selector (release). Das habe ich niedlich gemacht und habe es aus irgendeinem Code eingefügt, der dealloc umgewechselt hat, aber ich wollte das nicht im Beispiel zeigen, weil es einige spezielle Probleme damit gibt. Fest. –

+0

@LouisGerbarg Können Sie bitte einen Link zu Ihrer ursprünglichen Quelle, die swizzled release? – funroll

-1

Um etwas zu tun, wenn eine Methode aufgerufen wird, können Sie einen ereignisbasierten Ansatz versuchen. Wenn die Methode aufgerufen wird, sendet sie also ein Ereignis, das von beliebigen Listenern empfangen wird. Ich bin nicht gut mit Ziel C, aber ich habe gerade etwas Ähnliches herausgefunden, indem ich in Cocoa NSNotificationCenter verwende.

Aber wenn Sie mit "abfangen" meinen "Stopp", dann brauchen Sie vielleicht mehr Logik, um zu entscheiden, ob die Methode überhaupt aufgerufen werden soll.

+2

Ich denke, er bezieht sich auf Metaprogrammierungstechniken. – Geo

1

Vielleicht möchten Sie NSObject 's -forwardInvocation Methode. Auf diese Weise können Sie eine Nachricht erfassen, neu ausrichten und dann erneut senden.

+2

Die Dokumentation besagt, dass * -forwardInvocation * nur aufgerufen wird, wenn einem Objekt eine Nachricht gesendet wird, für die es keine entsprechende Methode hat. Ich möchte, dass eine Methode unter allen Umständen aufgerufen wird, selbst wenn der Empfänger diesen Selektor hat. – luvieere

+1

Dieser Kommentar hat deutlich mehr Informationen als deine ursprüngliche Frage ...;) – MarkPowell

+0

Ich bearbeite, danke für Hinweise. – luvieere

0

Ein Methodenaufruf, nein. Eine Nachricht senden, ja, aber Sie müssen viel aussagekräftiger sein, wenn Sie eine gute Antwort auf wie wollen.

+2

Der Begriff für einen Methodenaufruf in Obj-C ist "eine Nachricht senden", also sind sie eins und sie gleich, im Gegensatz zu einem Funktionsaufruf, der - Sie sind richtig - nicht dynamisch abgefangen werden kann. –

1

Sie können den Methodenaufruf mit einem eigenen Befehl überschreiben, der alles, was Sie beim "Abfangen" tun möchten, aufruft und zur ursprünglichen Implementierung aufruft. Swizzling wird mit class_replaceMethod() durchgeführt.

15

Abfangen von Methodenaufrufen in Objective-C (es ist ein Objective-C, kein C-Aufruf) erfolgt mit einer Methode namens Methode Swizzling.

Sie finden eine Einführung in die Implementierung dieses here. Ein Beispiel, wie das Swizzling von Methoden in einem realen Projekt implementiert wird, finden Sie unter OCMock (ein Isolation Framework für Objective-C).

11

eine Nachricht in Objective-C gesendet wird, in einen Aufruf der Funktion übersetzt objc_msgSend(receiver, selector, arguments) oder eine seiner Varianten objc_msgSendSuper, objc_msgSend_stret, objc_msgSendSuper_stret.

Wenn es möglich war, die Implementierung dieser Funktionen zu ändern, konnten wir jede Nachricht abfangen. Leider ist objc_msgSend Teil der Objective-C-Laufzeit und kann nicht überschrieben werden.

Durch Googeln fand ich ein Papier auf Google Books: A Reflective Architecture for Process Control Applications by Charlotte Pii Lunau. Das Papier führt einen Hack ein, indem der Klassenzeiger eines Objekts isa auf eine Instanz einer benutzerdefinierten MetaObject-Klasse umgeleitet wird. Nachrichten, die für das geänderte Objekt bestimmt waren, werden somit an die MetaObject-Instanz gesendet. Da die MetaObject-Klasse über keine eigenen Methoden verfügt, kann sie auf den Forward-Aufruf antworten, indem sie die Nachricht an das geänderte Objekt weiterleitet.

Das Papier enthält nicht die interessanten Teile des Quellcodes und ich habe keine Ahnung, ob ein solcher Ansatz in Cocoa Nebenwirkungen haben würde. Aber es könnte interessant sein, es zu versuchen.

2

eine Unterklasse von NSProxy erstellen und implementieren -forwardInvocation: und -methodSignatureForSelector: (oder -forwardingTargetForSelector:, wenn Sie es einfach auf ein zweites Objekt zu leiten sind auf stattdessen mit dem Verfahren von Hantieren selbst).

NSProxy ist eine Klasse für die Implementierung -forwardInvocation: auf. Es hat ein paar Methoden, aber meistens wollen Sie nicht, dass sie gefangen werden. Zum Beispiel würde das Abfangen der Referenzzählmethoden verhindern, dass der Proxy außer bei der Speicherbereinigung freigegeben wird. Wenn es jedoch spezifische Methoden für NSProxy gibt, die Sie unbedingt weiterleiten müssen, können Sie diese Methode explizit überschreiben und manuell aufrufen. Beachten Sie, dass einfach, weil eine Methode unter der NSProxy Dokumentation aufgeführt ist bedeutet nicht, dass NSProxy es implementiert, nur dass es erwartet wird, dass alle proxied Objekte haben es.

Wenn dies für Sie nicht funktioniert, geben Sie zusätzliche Details zu Ihrer Situation an.

5

Wenn Sie Nachrichten aus Ihrem Anwendungscode protokollieren möchten, ist der -forwardingTargetForSelector: Tipp Teil der Lösung.
Wickeln Sie Ihr Objekt:

@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 

Jetzt etwas tun:

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

und Sie werden ein Protokoll der Nachricht sendet haben. Beachten Sie, dass gesendete Nachrichten innerhalb des abgefangenen Objekts nicht abgefangen werden, da sie mit dem ursprünglichen Objekt "self" -Zeiger gesendet werden.

3

Wenn Sie nur Nachrichten von außen aufgerufen einzuloggen (in der Regel von den Delegierten genannt, welche Art von Nachrichten zu sehen, wenn, etc.), Sie respondsToSelector wie dies außer Kraft setzen können:

- (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; 

}

Verwandte Themen