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

时间:2022-09-13 10:53:58

UPDATE:

更新:

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

通过一些关键的建议和与George的反复交流,我想出了两种不同的方法来实现我在CodeRunner中想要的东西,并将它发布在Github的主旨站点:Objective-C AOP gist

The code is rough because it's a new concept and I just finished at 1:30am. It definitely works though and has some niceties like auto-adding all methods that aren't initializers, getters or setters. [END UPDATE]

代码很粗略,因为它是一个新概念,我刚在凌晨1:30完成。它确实可以工作,并且有一些优点,比如自动添加不是初始化器、getter或setter的所有方法。(最后更新)

Several times (but certainly not very often) I've come across a situation where my code would be a bit DRYer if I could call a context-sensitive piece of code for each method in a class. Use of the Objective-C runtime is totally fine, I'd accept C or C++ solutions as well.

有好几次(当然不是很经常),我遇到过这样的情况:如果我可以为类中的每个方法调用上下文敏感的代码,那么我的代码就有点干了。使用Objective-C运行时完全没问题,我也接受C或c++解决方案。

Instead of:

而不是:

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

Have something like this, with the result being the same:

有这样的东西,结果是一样的:

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

- (void)methodName1
{
   //more code
}

- (void)methodName2
{
   //more code
}

In a real-world application, AOPMethod would contain more code and there'd be more methods in the class.

在真实的应用程序中,AOPMethod将包含更多的代码,类中将包含更多的方法。

P.S., I'm fairly obsessed with DRY. Along with clarity of prose and performance it's a key component of how I assess my code's quality over the long term. For each new way I can avoid repeating myself, the benefit is exponential because I break off as much code as possible in reusable classes that are shared across many projects.

注:我非常喜欢干的。除了简洁和性能之外,它也是我如何长期评估代码质量的关键部分。对于每一种我可以避免重复自己的新方法,其好处是指数级的,因为我在许多项目*享的可重用类中尽可能多地中断代码。

1 个解决方案

#1


5  

For the specific use-case in the question, one could provide a handler that replaces the original implementation functions and calls before/after handlers as well as the original functions using something like this approach. In general however method implementation patching won't work as one would have to provide a handler/interception method for every intercepted method signature.

对于问题中的特定用例,可以提供一个处理程序,该处理程序使用类似这种方法替换原始实现函数和处理程序之前/之后的调用以及原始函数。然而,一般来说,方法实现补丁不能工作,因为每个拦截的方法签名都必须提供一个处理程序/拦截方法。

What would work more general (i.e. for everything except variable argument functions) would be handling -forwardInvocation:. The problem here though is that we would have to get that method invoked in the first place. As we can't remove methods in ObjC2, that can't be done in place.

更通用的方法(例如,除了变量参数函数之外的所有东西)是处理-forward调用:。但是这里的问题是我们必须首先调用那个方法。因为我们不能删除ObjC2中的方法,所以不能在适当的地方执行。

What can be done however is using proxies that implement forwardInvocation: and call our before/after handlers.

但是,可以使用实现forward调用的代理:并调用我们的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

As we don't need to return the actual instance from an init method, this could even be done transparently:

由于我们不需要从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

Now the following code:

现在下面的代码:

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

... will print:

…将打印:

forwardInvocation for: someFunction
before someFunction
some function called
after someFunction

向前调用:某函数在某个函数之前调用某个函数。

A runnable sample can be found in this gist.

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

#1


5  

For the specific use-case in the question, one could provide a handler that replaces the original implementation functions and calls before/after handlers as well as the original functions using something like this approach. In general however method implementation patching won't work as one would have to provide a handler/interception method for every intercepted method signature.

对于问题中的特定用例,可以提供一个处理程序,该处理程序使用类似这种方法替换原始实现函数和处理程序之前/之后的调用以及原始函数。然而,一般来说,方法实现补丁不能工作,因为每个拦截的方法签名都必须提供一个处理程序/拦截方法。

What would work more general (i.e. for everything except variable argument functions) would be handling -forwardInvocation:. The problem here though is that we would have to get that method invoked in the first place. As we can't remove methods in ObjC2, that can't be done in place.

更通用的方法(例如,除了变量参数函数之外的所有东西)是处理-forward调用:。但是这里的问题是我们必须首先调用那个方法。因为我们不能删除ObjC2中的方法,所以不能在适当的地方执行。

What can be done however is using proxies that implement forwardInvocation: and call our before/after handlers.

但是,可以使用实现forward调用的代理:并调用我们的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

As we don't need to return the actual instance from an init method, this could even be done transparently:

由于我们不需要从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

Now the following code:

现在下面的代码:

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

... will print:

…将打印:

forwardInvocation for: someFunction
before someFunction
some function called
after someFunction

向前调用:某函数在某个函数之前调用某个函数。

A runnable sample can be found in this gist.

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