Objective-C中的AOP:在保持DRY的同时将上下文感知代码注入到每个方法中

时间:2012-02-13 20:44:00

标签: c++ objective-c c aop dry

更新:

通过一些关键建议以及来回与乔治,我提出了两种不同的方法来实现我想要的CodeRunner并将其发布在Github的gist网站上: Objective-C AOP gist

代码粗糙,因为它是一个新概念,我刚刚在凌晨1:30结束。它肯定有效,并有一些细节,如自动添加所有非初始化器,getter或setter的方法。 [END UPDATE]

有几次(但肯定不是经常)我遇到过一种情况,如果我可以为类中的每个方法调用一个上下文相关的代码片段,那么我的代码会有点干扰。使用Objective-C运行时完全没问题,我也接受C或C ++解决方案。

而不是:

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

有这样的事情,结果是一样的:

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

- (void)methodName1
{
   //more code
}

- (void)methodName2
{
   //more code
}

在实际应用程序中,AOPMethod将包含更多代码,并且类中会有更多方法。

P.S。,我对DRY非常着迷。除了散文和表现的清晰度之外,它还是我长期评估代码质量的关键因素。对于每种新方法,我都可以避免重复自己,这种好处是指数级的,因为我在许多项目共享的可重用类中尽可能多地中断代码。

1 个答案:

答案 0 :(得分:5)

对于问题中的特定用例,可以提供一个处理程序来替换原始实现函数,并使用类似this approach之类的函数来调用处理程序之前/之后以及原始函数。通常情况下,方法实现修补不起作用,因为必须为每个截获的方法签名提供处理程序/拦截方法。

更通用的(即对于除变量参数函数之外的所有内容)将会处理-forwardInvocation:。这里的问题是我们必须首先调用该方法。由于我们无法删除ObjC2中的方法,因此无法完成。

然而,可以使用代理实现forwardInvocation:并调用我们的前/后处理程序。

@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

由于我们不需要从init方法返回实际实例,所以甚至可以透明地完成:

@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

现在输入以下代码:

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

...将打印:

  

forwardInvocation:someFunction
  在someFunction之前   一些称为
的函数   在someFunction之后

可以在this gist中找到可运行的样本。