AOP in Objective-C: inject context-sensitive code into each method while saving DRY

UPDATE:

With some key suggestions, back and forth with George, I came up with two different ways to achieve exactly what I want in CodeRunner, and posted it on the Github gist website: Objective-C AOP gist

The code is rude because it is a new concept, and I just finished at 1:30 in the morning. This definitely works, although it has some subtleties, such as automatically adding all methods that are not initializers, getters, or setters. [END UPDATE]

Several times (but, of course, not very often), I came across a situation where my code would be a bit DRYer if I could name the context-sensitive part of the code for each method in the class. Using Objective-C runtime is completely fine, I would make C or C ++ decisions.

Instead:

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

There is something like this, with the same result:

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

In a real application, AOPMethod will contain more code, and there will be more methods in the class.

PS, I'm pretty obsessed with DRY. Along with clarity of prose and performance, this is a key component of how I evaluate the quality of my code in the long run. For each new method, I can avoid repetition, the advantage is exponential, because I break as much code as possible in the reusable classes that are used in many projects.

+8
c ++ c objective-c aop dry
source share
1 answer

For a specific use case in a question, one could provide a handler that replaces the original implementation functions and calls before / after the handlers, as well as the original functions, using something like this to approach . However, as a rule, fixing a method implementation will not work as it would have to provide a handler / interception method for each intercepted method signature.

What will work more general (i.e. for everything except functions of variable arguments) will handle -forwardInvocation: The problem here is that we would have to get this method, which is called first. Since we cannot remove methods in ObjC2, this cannot be done in place.

However, you can use proxies that implement forwardInvocation: and call our handlers before / after.

 @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 

Since we do not need to return the actual instance from the init method, this can even be done transparently:

 @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 

Now the following code:

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

... will print:

forwardInvocation for: someFunction
before someFunction some function called after some function

The processed sample can be found in this value .

+5
source share

All Articles