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

jam*_*ack 8 c c++ aop dry objective-c

更新:

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

代码粗糙,因为它是一个新概念,我刚刚在凌晨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
}
Run Code Online (Sandbox Code Playgroud)

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

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

- (void)methodName1
{
   //more code
}

- (void)methodName2
{
   //more code
}
Run Code Online (Sandbox Code Playgroud)

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

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

Geo*_*che 5

对于问题中的特定用例,可以提供一个处理程序来替换原始实现函数并在处理程序之前/之后调用以及使用类似此方法之类的原始函数.一般而言,方法实现修补将不起作用,因为必须为每个截获的方法签名提供处理程序/拦截方法.

什么会更通用(即对于除变量参数函数之外的所有东西)将处理-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
Run Code Online (Sandbox Code Playgroud)

因为我们不需要从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
Run Code Online (Sandbox Code Playgroud)

现在以下代码:

Test *x = [[[Test alloc] init] autorelease];
[x someFunction];
Run Code Online (Sandbox Code Playgroud)

...将打印:

forwardInvocation for:someFunction
before someFunction 之后
调用someFunction的一些函数

可以在这个要点中找到可运行的样本.