2012-02-13 11 views
8

ACTUALIZACIÓN:AOP en Objective-C: inyectar código sensible al contexto en cada método, manteniendo SECO

Con algunas sugerencias clave y de ida y vuelta con George, yo he llegado con dos formas diferentes de logro exactamente lo que quiero en CodeRunner y lo publicó en el sitio quid de Github: Objective-C AOP gist

El código es áspera porque es un concepto nuevo y acabo de terminar a las 1:30 de la mañana. Sin embargo, definitivamente funciona y tiene algunas sutilezas, como agregar automáticamente todos los métodos que no son inicializadores, getters o setters. [FIN DE ACTUALIZACIÓN]

Varias veces (pero ciertamente no muy a menudo) He encontrado una situación en la que mi código podría ser un poco SECO si pudiera llamar a un fragmento de código contextual para cada método en una clase . El uso del tiempo de ejecución de Objective-C es totalmente correcto, aceptaría las soluciones C o C++ también.

En lugar de:

- (void)methodName1 
{ 
    self->selector = _cmd; 
    NSLog(@"This method is named: %@",_cmd); 
    //more code 
} 

- (void)methodName2 
{ 
    self->selector = _cmd; 
    NSLog(@"This method is named: %@",_cmd); 
    //more code 
} 

tener algo como esto, con el resultado de la misma:

+ (void)AOPMethod 
{ 
    self->selector = _cmd; 
    NSLog(@"This method is named: %@",_cmd); 
} 

- (void)methodName1 
{ 
    //more code 
} 

- (void)methodName2 
{ 
    //more code 
} 

En una aplicación real, AOPMethod contendría más de código y no habría más métodos en la clase.

P.S., estoy bastante obsesionado con DRY. Junto con la claridad de la prosa y el rendimiento, es un componente clave de cómo evalúo la calidad de mi código a largo plazo. Por cada nueva forma que puedo evitar de repetirme, el beneficio es exponencial porque rompo la mayor cantidad de código posible en clases reutilizables que se comparten en muchos proyectos.

+0

¿Está buscando intercepciones de entrada/salida? –

+0

@GeorgFritzsche Gracias por la pregunta. Si la interceptación de un método es independiente de la plataforma y permitirá el uso de datos sensibles al contexto a nivel de método como _cmd dentro de cada método de una clase y sin duplicación de código, entonces sí. En otras palabras, si puede compartir una técnica en uno de los idiomas base mencionados (y no en los marcos no disponibles en todas las plataformas) que permitirán que alguna versión del último ejemplo tenga el mismo resultado que la anterior, entonces eso es responder. Gracias de nuevo. –

+0

Para su caso de uso específico (todos los métodos con la misma firma) [este enfoque] (http://stackoverflow.com/questions/9242571/copy-a-method-imp-for-multiple-method-swizzles) podría ser extendido para parchar todos los métodos adecuados en la lista de métodos. Por el momento no puedo pensar en una solución elegante más general, solo ineficaz. –

Respuesta

5

Para el caso de uso específico en la pregunta, se podría proporcionar un controlador que reemplace las funciones de implementación originales y llamadas antes/después de los controladores, así como las funciones originales usando algo como this approach. En general, sin embargo, el parcheo de la implementación del método no funcionará, ya que uno tendría que proporcionar un método de manejo/interceptación para cada firma de método interceptado.

Lo que funcionaría más en general (es decir, para todo, excepto las funciones de argumento variable) sería el manejo de -forwardInvocation:. El problema aquí es que tendríamos que invocar ese método en primer lugar. Como no podemos eliminar los métodos en ObjC2, eso no se puede hacer en el lugar.

Lo que se puede hacer, sin embargo, es usar proxies que implementen forwardInvocation: y llamar a nuestros manejadores de antes/después.

@interface AspectProxy : NSProxy { 
    id target_; 
} 
- (id)initWithTarget:(id)target; 
@end 

@implementation AspectProxy 
- (id)initWithTarget:(id)target { 
    target_ = [target retain]; 
    return self; 
} 
- (void)dealloc { 
    [target_ release]; 
    [super dealloc]; 
} 
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { 
    return [target_ methodSignatureForSelector:sel]; 
} 
- (void)forwardInvocation:(NSInvocation *)inv { 
    SEL sel = [inv selector]; 
    NSLog(@"forwardInvocation for: %@", NSStringFromSelector(sel)); 
    if (sel == @selector(aspectBefore:) || sel == @selector(aspectAfter:)) { 
     return; 
    } 
    if ([target_ respondsToSelector:@selector(aspectBefore:)]) { 
     [target_ performSelector:@selector(aspectBefore:) withObject:inv]; 
    } 
    [inv invokeWithTarget:target_]; 
    if ([target_ respondsToSelector:@selector(aspectAfter:)]) { 
     [target_ performSelector:@selector(aspectAfter:) withObject:inv]; 
    } 
} 
@end 

Como no es necesario devolver el ejemplo real de un método init, esto incluso se podría hacer más transparente:

@interface Test : NSObject 
- (void)someFunction; 
@end 

@implementation Test 
- (id)init { 
    if (self = [super init]) { 
     return [[AspectProxy alloc] initWithTarget:[self autorelease]]; 
    } 
    return self; 
} 
- (void)aspectBefore:(NSInvocation *)inv { 
    NSLog(@"before %@", NSStringFromSelector([inv selector])); 
} 
- (void)aspectAfter:(NSInvocation *)inv { 
    NSLog(@"after %@", NSStringFromSelector([inv selector])); 
} 
- (void)someFunction { 
    NSLog(@"some function called"); 
} 
@end 

Ahora el siguiente código:

Test *x = [[[Test alloc] init] autorelease]; 
[x someFunction]; 

. ..imprimirá:

forwardInvocation para: algunaFuncion
antes algunaFuncion
alguna función llamada
después algunaFuncion

Una muestra ejecutable se puede encontrar en this gist.

+1

Esta es una solución realmente genial. Hice algo así una vez para crear un proxy de delegado que me permite evitar tener que escribir 'if ([delegar respondsToSelector:]) {}' para los métodos opcionales. ¡Funciona genial! – Alex

+0

Terminé utilizando forwardInvocation & NSProxy como una de las opciones (que me enteré poco después de publicar la pregunta), pero estaba más obsesionado con el uso del tiempo de ejecución, así que se me ocurrió una manera de hacerlo también. Ambos métodos están vinculados en la esencia en mi pregunta. Me gusta lo limpia que está su versión de NSProxy. –

Cuestiones relacionadas