2009-10-24 21 views
18

¿Puedo interceptar una llamada a método en Objective-C? ¿Cómo?Llamada a método de interceptación en Objective-C

Editar: respuesta Mark Powell 's me dio una solución parcial, el método -forwardInvocation. Pero la documentación indica que -forewardInvocation solo se invoca cuando se envía un objeto a un mensaje para el que no tiene un método correspondiente. Me gustaría llamar a un método bajo cualquier circunstancia, incluso si el receptor tiene ese selector.

Respuesta

22

Lo haces volteando la llamada al método.Suponiendo que desee capturar todas las emisiones a 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]:"); 

usted puede obtener más detalles en el tiempo de ejecución de Objective C documentation.

+0

Probablemente significó @selector (release), no @selector (dealloc) –

+0

Sí, quise decir @selector (release). Esto me encanta y lo pego de algún código que desató dealloc, pero no quería mostrarlo en el ejemplo, porque hay algunos problemas especiales para hacerlo. Fijo. –

+0

@LouisGerbarg ¿Puede por favor publicar un enlace a su fuente original que se liberó? – funroll

-1

Para hacer algo cuando se llama a un método, puede probar un enfoque basado en eventos. Entonces, cuando se llama al método, transmite un evento, que es captado por cualquier oyente. No soy bueno con el objetivo C, pero descubrí algo similar usando NSNotificationCenter en Cocoa.

Pero si por "intercepción" quiere decir "detener", entonces tal vez necesite más lógica para decidir si el método debe llamarse en absoluto.

+2

Creo que se está refiriendo a las técnicas de metaprogramación. – Geo

1

Quizás desee el método NSObject-forwardInvocation. Esto le permite capturar un mensaje, redirigirlo y luego volver a enviarlo.

+2

La documentación indica que * -forwardInvocation * solo se invoca cuando se envía un objeto a un mensaje para el que no tiene un método correspondiente. Me gustaría llamar a un método bajo cualquier circunstancia, incluso si el receptor tiene ese selector. – luvieere

+1

Este comentario tiene mucha más información que tu pregunta original ...;) – MarkPowell

+0

Voy a editar, gracias por señalar. – luvieere

0

Llamada a un método, no. Un mensaje enviado, sí, pero tendrá que ser mucho más descriptivo si quiere una buena respuesta sobre cómo.

+2

El término para una llamada a un método en Obj-C es "enviar un mensaje" para que sean uno y lo mismo, a diferencia de una llamada a función que, está en lo cierto, no puede ser interceptada dinámicamente. –

1

Puede cambiar la llamada de método por una propia, que hace lo que quiera hacer en "interceptación" y llamadas a través de la implementación original. Swizzling está hecho con class_replaceMethod().

15

Llamadas al método de interceptación en Objective-C (suponiendo que es un Objective-C, no una llamada C) se realiza con una técnica llamada método swizzling.

Puede encontrar una introducción sobre cómo implementar ese here. Por ejemplo, cómo se implementa el swizzling del método en un proyecto real check out OCMock (un marco de aislamiento para Objective-C).

11

Envío de un mensaje en Objective-C se traduce en una llamada de la función objc_msgSend(receiver, selector, arguments) o una de sus variantes, objc_msgSendSuperobjc_msgSend_stret, objc_msgSendSuper_stret.

Si fue posible cambiar la implementación de estas funciones, podríamos interceptar cualquier mensaje. Desafortunadamente, objc_msgSend es parte del tiempo de ejecución de Objective-C y no se puede anular.

Al buscar en Google encontré un documento en Google Books: A Reflective Architecture for Process Control Applications by Charlotte Pii Lunau. El documento presenta un truco al redirigir el puntero de clase isa de un objeto a una instancia de una clase MetaObject personalizada. Los mensajes que estaban destinados para el objeto modificado se envían a la instancia de MetaObject. Como la clase MetaObject no tiene sus propios métodos, puede responder a la invocación de reenvío reenviando el mensaje al objeto modificado.

El documento no incluye los bits interesantes del código fuente y no tengo idea si tal enfoque tendría efectos secundarios en Cocoa. Pero podría ser interesante intentarlo.

2

crear una subclase de NSProxy y aplicar -forwardInvocation: y -methodSignatureForSelector: (o -forwardingTargetForSelector:, si simplemente dirigiéndolo a un segundo objeto en lugar de tocar el violín con el método usted mismo).

NSProxy es una clase diseñada para implementar -forwardInvocation: en. Tiene algunos métodos, pero la mayoría no quiere que los atrapen. Por ejemplo, la captura de los métodos de recuento de referencias evitaría que el proxy fuera desasignado excepto en la recolección de basura. Pero si hay métodos específicos en NSProxy que absolutamente necesita reenviar, puede anular ese método específicamente y llamar al -forwardInvocation: manualmente. Tenga en cuenta que, simplemente porque un método se enumera en la documentación NSProxy, no significa que NSProxy lo implementa, simplemente que se espera que todos los objetos con proxy lo tengan.

Si esto no funciona, proporcione detalles adicionales sobre su situación.

5

Si desea registrar envíos de mensajes desde el código de su aplicación, el -forwardingTargetForSelector: tip es parte de la solución.
Envuelva su objeto:

@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 

Ahora hacer algo como esto:

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

y usted tendrá un registro de mensajes envía. Tenga en cuenta que los envíos enviados desde dentro del objeto interceptado no serán interceptados, ya que se enviarán usando el puntero "propio" del objeto original.

3

Si sólo desea registrar mensajes de llamadas desde el exterior (por lo general llamada de los delegados; para ver qué tipo de mensajes, cuándo, etc.), puede anular respondsToSelector así:

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

}

Cuestiones relacionadas