AOP(Aspect-Oriented Programming)切面編程在編程中熱點話題之一,通過AOP可以對業務邏輯的各個部分進行隔離,降低業務邏輯之間的耦合度,提高程序重用性,同時提高開發效率.
Aspect-Oriented Programming (AOP) complements Object-Oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization of concerns such as transaction management that cut across multiple types and objects. (Such concerns are often termed crosscutting concerns in AOP literature.)
AOP不是新的技術,是OOP的一種延續,作為切入方向,可以降低公用模塊和業務邏輯的之間的耦合關系,開發中實際應用場景:日志記錄,性能統計,安全控制,事務處理,異常處理...,對于這些通用功能如果直接嵌套在已有的項目中,不斷會影響開發效率,而且會導致后期項目的可讀性和維護性都大打折扣.
Method Swizzling
Objective-C 中實現AOP可以通過利用 Runtime 特性給指定的方法添加自定義代碼。Method Swizzling 其實很常見,就是在load的時候將兩個方法的實現進行交換,第三方庫全屏返回手勢就是通過交換viewWillAppear實現的.
新增UIViewController分類,方法交換:
<pre><code>`@implementation UIViewController (AOP)
- (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
id obj = [[self alloc] init];
[obj swizzleMethod:@selector(viewWillAppear:) withMethod:@selector(aop_viewWillAppear:)];
[obj swizzleMethod:@selector(viewDidAppear:) withMethod:@selector(aop_viewDidAppear:)];
});
}
(void)aop_viewWillAppear:(BOOL)animated {
NSLog(@"FlyElephant--aop_viewWillAppear方法執行");
}(void)aop_viewDidAppear:(BOOL)test {
NSLog(@"FlyElephant--aop_viewDidAppear方法執行");
}-
(void)swizzleMethod:(SEL)origSelector withMethod:(SEL)newSelector {
Class class = [self class];Method originalMethod = class_getInstanceMethod(class, origSelector);
Method swizzledMethod = class_getInstanceMethod(class, newSelector);BOOL didAddMethod = class_addMethod(class,
origSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
newSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
@end`</code></pre>
ViewController中方法注意要實現父類方法:
<pre><code>`- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"viewWillAppear---正常顯示");
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"viewDidAppear---正常展示");
}`</code></pre>
Aspects
Aspects是一個常用的AOP庫,通過Runtime封裝了兩個簡單的API,類方法和實例方法.
<pre><code>`+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
/// Adds a block of code before/instead/after the current selector
for a specific instance.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;`</code></pre>
viewDidAppear之后設置回調:
<pre><code>[self aspect_hookSelector:@selector(viewDidAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info) { NSLog(@"viewDidAppear---執行完成之后的回調:%@",info); } error:nil];
</code></pre>
實現按鈕點擊之后的事件獲取:
<pre><code>` [self aspect_hookSelector:@selector(buyAction:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info,NSDictionary *dict){
NSLog(@"FlyElephant---購買的參數:%@",info.arguments);
} error:nil];
[self aspect_hookSelector:@selector(goBuy:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info,NSDictionary *dict){
NSLog(@"FlyElephant---參數:%@",info.arguments);
NSLog(@"FlyElephant---參數:%@",dict);
} error:nil];`</code></pre>
點擊執行代碼:
<pre><code>`- (IBAction)buyAction:(UIButton *)sender {
NSDictionary *dict = @{@"productName":@"apple"};
[self goBuy:dict];
}
- (void)goBuy:(NSDictionary *)dict {
NSLog(@"購買開始啦");
}`</code></pre>
AOP在埋點開發中會涉及到很多公用的業務,如果每次都在代碼中嵌入Aspect代碼,可以進行再一次封裝.
<pre><code>`static NSString * const MobEventClassName = @"MobEventClassName";
static NSString * const MobEventClassDescription = @"MobEventClassDescription";
static NSString * const MobEventClassEvents = @"MobEventClassEvents";
static NSString * const MobEventClassEventName = @"MobEventClassEventName";
static NSString * const MobEventSelectorName = @"MobEventSelectorName";
static NSString * const MobEventSelectorBlock = @"MobEventSelectorBlock";
@interface AppDelegate (MobEvent)
- (void)setupAnalytics:(NSDictionary *)configs;
@end`</code></pre>
<pre><code>`typedef void (^AspectHandlerBlock)(id<AspectInfo> aspectInfo);
@implementation AppDelegate (MobEvent)
- (void)setupAnalytics:(NSDictionary *)configs {
// Hook Page Impression
// Hook Events
for (NSString *className in configs) {
Class clazz = NSClassFromString(className);
NSDictionary *config = configs[className];
if (config[MobEventClassEvents]) {
for (NSDictionary *event in config[MobEventClassEvents]) {
SEL selekor = NSSelectorFromString(event[MobEventSelectorName]);
AspectHandlerBlock block = event[MobEventSelectorBlock];
[clazz aspect_hookSelector:selekor
withOptions:AspectPositionAfter
usingBlock:^(id<AspectInfo> aspectInfo) {
// 也可以不設置Block
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
block(aspectInfo);
});
} error:NULL];
}
}
}
}
@end`</code></pre>
AppDelegate中初始化代碼:
<pre><code>`- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
NSDictionary *config = @{
@"ViewController": @{
MobEventClassDescription: @"首頁",
MobEventClassEvents: @[
@{
MobEventClassEventName: @"首頁Didload進入",
MobEventSelectorName: @"viewDidLoad",
MobEventSelectorBlock: ^(id<AspectInfo> aspectInfo) {
NSLog(@"Config代理執行");
},
}
]
}
};
[self setupAnalytics:config];
return YES;
}`</cod></pre>
實際開發中第一種和第二種都可能需要結合起來,并不是單一的就是滿足開發中各種業務需求~