解耦套路之面向切面编程

2020/01/15 DesignPatterns 共 2754 字,约 8 分钟

AOP 是 Java Spring 框架另一个重要的功能,AOP(Aspect Oriented Programming)意为:面向切面编程,它是一种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想,本文用于学习这种编程思想,并给出 iOS 中的实现。

为什么需要 AOP

iOS 开发当中不可避免的要与UIViewController打交道,一个项目当中可能有很多UIViewController的子类,像下面这样:

// ViewControllerA
@interface ViewControllerA:UIViewController

@end

@implementation ViewControllerA

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

@end

// ViewControllerB
@interface ViewControllerB:UIViewController

@end

@implementation ViewControllerB

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}
@end

如果需要在viewDidLoad方法里进行一些统计,使用面向对象的思路,我们通常会这样做:

//添加一个统计类
@interface Reporter:NSObject
- (void)record:(NSString *)message;
@end
@implementation Reporter

- (void)record:(NSString *)message{
    // do something
}
@end

@interface ViewControllerA:UIViewController
@end
@implementation ViewControllerA

- (void)viewDidLoad {
    [super viewDidLoad];
    //调用统计代码
    Reporter *reporter = [Reporter new];
    [reporter record:@"something..."];
}
@end

@interface ViewControllerB:UIViewController
@end
@implementation ViewControllerB

- (void)viewDidLoad {
    [super viewDidLoad];
    //调用统计代码
    Reporter *reporter = [Reporter new];
    [reporter record:@"something..."];
}
@end

这样其实已经产生了耦合,每个UIViewControllerviewDidLoad方法都要写上Reporter的代码,当需要记录的信息越来越多时就会变得很臃肿。

或许你会想,我建个这样的基类不就可以了吗?

@interface RootController:UIViewController
@end
@implementation RootController

- (void)viewDidLoad {
    [super viewDidLoad];
    Reporter *reporter = [Reporter new];
    [reporter record:@"something..."];
}
@end

@interface ViewControllerA:RootController
@end
@implementation ViewControllerA

- (void)viewDidLoad {
    [super viewDidLoad];
}
@end

@interface ViewControllerB:RootController
@end
@implementation ViewControllerB

- (void)viewDidLoad {
    [super viewDidLoad];
}
@end

确实,理论上需要用 AOP 解决的问题,OOP 也可以解决,但这并非是高效的做法,这样做就需要多维护一个RootController类,并且如果其他工程需要用到ViewControllerAViewControllerB,也不方便直接移植过去。

这个问题的本质在于如何增强对象的方法,OOP 可以解决,但 AOP 可以更加高效优雅的解决。

使用 AOP 增强对象的方法

AOP 思想是在在运行时,动态地将代码切入到类的指定方法、指定位置上,以此来增强对象的方法

本文示例采用的是 OC 语言,这里使用 Aspects 库来解决这个问题:

[UIViewController aspect_hookSelector:@selector(viewDidLoad:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
    Reporter *reporter = [Reporter new];
    [reporter record:@"something..."];
} error:NULL];
@end

这里使用 AOP 切入了UIViewControllerviewDidLoad方法后,补充调用了Reporter,轻松实现了解耦。

Aspects 库是基于runtime实现的,提供了更完善的切入时机:

typedef NS_OPTIONS(NSUInteger, AspectOptions) {
    AspectPositionAfter   = 0,            /// Called after the original implementation (default)
    AspectPositionInstead = 1,            /// Will replace the original implementation.
    AspectPositionBefore  = 2,            /// Called before the original implementation.
    
    AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution.
};

AOP 的用途

AOP 减少了重复代码和耦合,结构更加清晰,在这些场景使用会带来不错的效果:

  • 日志输出
  • 事件统计
  • 权限检查

需要注意的是,AOP 拦截了所有的事件,使用不当可能会带一些意想不到的结果。

如果有需求要用到 AOP,最好选择一个成熟完善的框架,框架内部通常会提供较完善的异常处理和测试,可以帮助我们写出更完善和安全的代码。

iOS 开发强烈推荐使用 Aspects,具体使用和注意事项参考文档即可。

文档信息

Search

    Table of Contents