objective c - Método de intercepción de llamada en Objective-C
methods intercept (10)
¿Puedo interceptar una llamada de método en Objective-C? ¿Cómo?
Edición: la respuesta de Mark Powell me dio una solución parcial, el método de invasión hacia adelante . Pero la documentación indica que -forwardInvocation solo se invoca cuando a un objeto se le envía un mensaje para el que no tiene un método correspondiente. Me gustaría que se llame a un método en cualquier circunstancia, incluso si el receptor tiene ese selector.
Cree una subclase de NSProxy
e implemente -forwardInvocation:
y -methodSignatureForSelector:
(o -forwardingTargetForSelector:
si simplemente lo está dirigiendo a un segundo objeto en lugar de jugar con el método usted mismo).
NSProxy
es una clase diseñada para implementar -forwardInvocation:
en. Tiene algunos métodos, pero en su mayoría no quieres que los atrapen. Por ejemplo, la captura de los métodos de conteo de referencia evitaría que el proxy se desasigne, 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 a -forwardInvocation:
manualmente. Tenga en cuenta que simplemente porque un método esté listado en la documentación de NSProxy
no significa que NSProxy
implemente, simplemente que se espera que todos los objetos con proxy lo tengan.
Si esto no funciona para usted, brinde detalles adicionales sobre su situación.
El 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_msgSendSuper
, objc_msgSend_stret
, objc_msgSendSuper_stret
.
Si fuera 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 artículo en Google Books: Una arquitectura reflexiva para aplicaciones de control de procesos por Charlotte Pii Lunau . El documento introduce un hack al redireccionar el puntero de clase isa
un objeto a una instancia de una clase de MetaObject personalizada. Los mensajes destinados al objeto modificado se envían a la instancia de MetaObject. Como la clase MetaObject no tiene métodos propios, puede responder a la invocación hacia adelante reenviando el mensaje al objeto modificado.
El documento no incluye los fragmentos interesantes del código fuente y no tengo idea si este enfoque tendría efectos secundarios en Cocoa. Pero podría ser interesante intentarlo.
La intercepción de llamadas de método en Objective-C (asumiendo que es un Objective-C, no una llamada de C) se realiza con una técnica llamada método swizzling.
Puedes encontrar una introducción sobre cómo implementar eso here . Para ver un ejemplo de cómo se implementa el método swizzling en un proyecto real, consulte OCMock (un marco de aislamiento para Objective-C).
Lo haces por swizzling el método de llamada. Suponiendo que desea capturar todos los lanzamientos 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, "v@:");
Puede obtener más detalles en la documentation tiempo de ejecución de Objective C.
Para hacer algo cuando se llama a un método, puede probar un enfoque basado en eventos. Entonces, cuando se llama al método, se emite un evento, que es escuchado por cualquier oyente. No soy muy bueno con el objetivo C, pero acabo de descubrir algo similar utilizando NSNotificationCenter en Cocoa.
Pero si "interceptar" quiere decir "detener", entonces tal vez necesite más lógica para decidir si el método debería ser llamado en absoluto.
Puede intercambiar la llamada al método con uno de los suyos propios, que hace lo que quiera hacer en la "intercepción" y se comunica con la implementación original. Swizzling se realiza con class_replaceMethod ().
Si desea registrar los envíos de mensajes desde el código de su aplicación, el consejo -forwardingTargetForSelector: es parte de la solución.
Envuelve tu 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 haz algo como esto:
Interceptor *i = [[[Interceptor alloc] init] autorelease];
NSFetchedResultsController *controller = [self setupFetchedResultsController];
i.interceptedTarget = controller;
controller = (NSFetchedResultsController *)i;
y tendrás un registro de mensajes enviados. Tenga en cuenta que los envíos enviados desde dentro del objeto interceptado no se interceptarán, ya que se enviarán utilizando el puntero "self" del objeto original.
Si solo desea registrar los mensajes llamados desde el exterior (generalmente llamados de los delegados; para ver qué tipo de mensajes, cuándo, etc.), puede anular respondsToSelector de la siguiente manera:
- (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;
}
Tal vez quiera el método de -forwardInvocation
. Esto le permite capturar un mensaje, redirigirlo y luego reenviarlo.
Un método llamado, no. Un mensaje enviado, sí, pero tendrás que ser mucho más descriptivo si quieres una buena respuesta sobre cómo.