1.什么是面向切面编程
AOP(Aspect-Oriented Programming),面向切面编程,看着是跟OOP(面向对象编程)挺相近的,但实际上又有什么区别呢?OOP具有封装,继承,多态等东西来定义从上到下这种层次关系,但要想实现从左到右的关系的话就开始有点水土不服了,例如用户的权限控制,操作日志等,这些与我们要实现的核心功能不大有关系的东西散布在我们代码的周边,显示十分不好看。于是我们引入了AOP的模式。
我们通常在实现一个页面逻辑的时候,通常伴随着操作日志,安全监测,事务处理等几个逻辑,在实现逻辑的时候都要写一大堆这种代码。而AOP就是将这些与主体业务无关,但又有为业务提供服务的逻辑代码封装起来,降低模块之间的耦合度。如图所示中的圆柱体好比如我们的业务流程,aop代表的是那个横向的操作,俗称切面编程。或许上面的这些理论有点头疼,对于AOP我的大体理解是:将那些与业务核心不大相关的杂七杂八的东西独立开,每次实现了业务核心之前或之后,调用一下对应的方法。
注意:AOP不是一种技术,实际上是编程思想。凡是符合AOP思想的技术,都可以看成是AOP的实现
2.AOP在iOS中的应用
在 Objective-C 的世界里,有很多方式可以实现 AOP ,Method Swizzling 就是其中之一。而且幸运的是,目前已经有一些第三方库可以让你不需要了解 Runtime ,就能直接开始使用 AOP 。
Aspects 就是一个不错的 AOP 库,封装了 Runtime , Method Swizzling 这些黑色技巧,只提供两个简单的API:
+ (id<aspecttoken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;- (id<aspecttoken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;</aspecttoken></aspecttoken>
使用 Aspects 提供的 API,我们之前的例子会进化成这个样子:
@implementation UIViewController (Logging)+ (void)load
{
[UIViewController aspect_hookSelector:@selector(viewDidAppear:)
withOptions:AspectPositionAfter
usingBlock:^(id<aspectinfo> aspectInfo) { NSString *className = NSStringFromClass([[aspectInfo instance] class]);
[Logging logWithEventName:className];
} error:NULL];
}</aspectinfo>
你可以用同样的方式在任何你感兴趣的方法里添加自定义代码,比如 IBAction 的方法里。更好的方式,你提供一个 Logging 的配置文件作为唯一处理事件记录的地方:
@implementation AppDelegate (Logging)+ (void)setupLogging
{ NSDictionary *config = @{ @"MainViewController": @{
GLLoggingPageImpression: @"page imp - main page",
GLLoggingTrackedEvents: @[
@{
GLLoggingEventName: @"button one clicked",
GLLoggingEventSelectorName: @"buttonOneClicked:",
GLLoggingEventHandlerBlock: ^(id<aspectinfo> aspectInfo) {
[Logging logWithEventName:@"button one clicked"];
},
},
@{
GLLoggingEventName: @"button two clicked",
GLLoggingEventSelectorName: @"buttonTwoClicked:",
GLLoggingEventHandlerBlock: ^(id<aspectinfo> aspectInfo) {
[Logging logWithEventName:@"button two clicked"];
},
},
],
}, @"DetailViewController": @{
GLLoggingPageImpression: @"page imp - detail page",
}
};
[AppDelegate setupWithConfiguration:config];
}
+ (void)setupWithConfiguration:(NSDictionary *)configs
{ // Hook Page Impression
[UIViewController aspect_hookSelector:@selector(viewDidAppear:)
withOptions:AspectPositionAfter
usingBlock:^(id<aspectinfo> aspectInfo) { NSString *className = NSStringFromClass([[aspectInfo instance] class]);
[Logging logWithEventName:className];
} error:NULL]; // Hook Events
for (NSString *className in configs) {
Class clazz = NSClassFromString(className); NSDictionary *config = configs[className]; if (config[GLLoggingTrackedEvents]) { for (NSDictionary *event in config[GLLoggingTrackedEvents]) {
SEL selekor = NSSelectorFromString(event[GLLoggingEventSelectorName]);
AspectHandlerBlock block = event[GLLoggingEventHandlerBlock];
[clazz aspect_hookSelector:selekor
withOptions:AspectPositionAfter
usingBlock:^(id<aspectinfo> aspectInfo) {
block(aspectInfo);
} error:NULL];
}
}
}
}</aspectinfo></aspectinfo></aspectinfo></aspectinfo>
然后在 -application:didFinishLaunchingWithOptions:
里调用 setupLogging:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch.
[self setupLogging]; return YES;
}
最后的话
利用 objective-C Runtime 特性和 Aspect Oriented Programming ,我们可以把琐碎事务的逻辑从主逻辑中分离出来,作为单独的模块。它是对面向对象编程模式的一个补充。Logging 是个经典的应用,这里做个抛砖引玉,发挥想象力,可以做出其他有趣的应用。
使用 Aspects 完整的例子可以从这里获得:AspectsDemo。