概念
AOP編程也叫面向切面編程,通過(guò)預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)萍鲸。AOP是OOP的延續(xù)是函數(shù)式編程的一種衍生范型。主要作用對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離炮障,解耦槐壳。就是一種解耦的思想。
典型業(yè)務(wù)范圍
- 日志記錄
- 性能統(tǒng)計(jì)
- 用戶行為數(shù)據(jù)統(tǒng)計(jì)
- 權(quán)限控制
iOS下的實(shí)現(xiàn)
網(wǎng)上關(guān)于這方面的例子很多仔役,都是云里霧里掷伙,你就覺(jué)得很牛逼,然后就總覺(jué)得少了什么又兵,不得要領(lǐng)任柜,看到這博文的你,算有福了沛厨,本教程舉個(gè)簡(jiǎn)單的日志記錄系統(tǒng)宙地,演示AOP的工作流程,廢話不說(shuō)上碼逆皮。
建立工程項(xiàng)目命名 AOP ViewController代碼如下
ViewController.h
@interface ViewController : UIViewController
@end
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)viewDidAppear:(BOOL)animated{
}
@end
只有一個(gè)按鈕點(diǎn)擊事件宅粥,其它啥也沒(méi)有。
再建一個(gè)統(tǒng)計(jì)類 Stastic
Stastic.h
#import <Foundation/Foundation.h>
@interface Stastic : NSObject
+ (void)stasticWithEventName:(NSString *)eventName;
@end
Stastic.m
#import "Stastic.h"
@implementation Stastic
+ (void)stasticWithEventName:(NSString *)eventName{
NSLog(@"-----> %@",eventName);
}
@end
開始事件的統(tǒng)計(jì),一般做法都是如下,表示用戶瀏覽了ViewController頁(yè)面
- (void)viewDidAppear:(BOOL)animated{
[Stastic stasticWithEventName:@"ViewController"];
}
AOP做法
建立ViewController類別
UIViewController+Stastic.h
#import <UIKit/UIKit.h>
@interface UIViewController (Stastic)
@end
UIViewController+Stastic.m
#import "UIViewController+Stastic.h"
#import "Stastic.h"
#import <objc/runtime.h>
#import <objc/objc.h>
@implementation UIViewController (Stastic)
+ (void)load{
swizzleMethod([self class], @selector(viewDidAppear:), @selector(swizzled_viewDidAppear:));
}
- (void)swizzled_viewDidAppear:(BOOL)animated{
// call original implementation
[self swizzled_viewDidAppear:animated];
// Begin Stastic Event
[Stastic stasticWithEventName:@"UIViewController"];
}
void swizzleMethod(Class class,SEL originalSelector,SEL swizzledSelector){
// the method might not exist in the class, but in its superclass
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// class_addMethod will fail if original method already exists
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// the method doesn’t exist and we just added one
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
@end
swizzled_viewDidAppear:(BOOL)animated 里面又調(diào)用了自己看起來(lái)像遞歸页屠,實(shí)際上因?yàn)閠untime的作用函數(shù)實(shí)現(xiàn)已經(jīng)被交換了粹胯。調(diào)用 viewDidAppear: 會(huì)調(diào)用你實(shí)現(xiàn)的 swizzled_viewDidAppear:,而在 swizzled_viewDidAppear: 里調(diào)用 swizzled_viewDidAppear: 實(shí)際上調(diào)用的是原來(lái)的 viewDidAppear:
先嘗試添加原 selector 是為了做一層保護(hù)辰企,因?yàn)槿绻@個(gè)類沒(méi)有實(shí)現(xiàn) originalSelector 风纠,但其父類實(shí)現(xiàn)了,那 class_getInstanceMethod 會(huì)返回父類的方法牢贸。這樣 method_exchangeImplementations 替換的是父類的那個(gè)方法竹观,這當(dāng)然不是你想要的。所以我們先嘗試添加 orginalSelector ,如果已經(jīng)存在臭增,再用 method_exchangeImplementations 把原方法的實(shí)現(xiàn)跟新的方法實(shí)現(xiàn)給交換掉懂酱。
類別里添加 +load: 方法,然后在 +load: 里把 viewDidAppear 給替換掉:
一般情況下誊抛,類別里的方法會(huì)重寫掉主類里相同命名的方法列牺。如果有兩個(gè)類別實(shí)現(xiàn)了相同命名的方法,只有一個(gè)方法會(huì)被調(diào)用拗窃。但 +load: 是個(gè)特例瞎领,當(dāng)一個(gè)類被讀到內(nèi)存的時(shí)候, runtime 會(huì)給這個(gè)類及它的每一個(gè)類別都發(fā)送一個(gè) +load: 消息随夸。也就是說(shuō)一個(gè)類別里面實(shí)現(xiàn)了+load方法九默,就能自動(dòng)消息。
經(jīng)過(guò)以上幾個(gè)步驟宾毒,我們就實(shí)現(xiàn)了添加日志記錄的解耦操作驼修,沒(méi)有動(dòng)到原始類的任何代碼。這樣一個(gè)過(guò)程就叫AOP編程诈铛,AOP是一種思想乙各。
iOS業(yè)界AOP框架
工程添加Podfile
target 'AOP' do
pod 'Aspects'
end
實(shí)現(xiàn)代碼如下
+ (void)load{
[UIViewController aspect_hookSelector:@selector(viewDidAppear:)
withOptions:AspectPositionAfter
usingBlock:^(id<AspectInfo>aspectInfo){
[Stastic stasticWithEventName:@"UIViewController"];
} error:nil];
}
AOP框架之 Aspects 打點(diǎn)統(tǒng)計(jì)
建兩個(gè)測(cè)試類Test1ViewController,Test2ViewController
Test1ViewController.m
#import "Test1ViewController.h"
#import "Test2ViewController.h"
@interface Test1ViewController ()
@end
@implementation Test1ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"Test1ViewController";
self.view.backgroundColor = [UIColor redColor];
UIButton *btn1 = [[UIButton alloc] initWithFrame:CGRectMake(100, 80, 100, 80)];
[btn1 setTitle:@"Press_One" forState:UIControlStateNormal];
btn1.backgroundColor = [UIColor redColor];
[btn1 addTarget:self action:@selector(action1) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn1];
UIButton *btn2 = [[UIButton alloc] initWithFrame:CGRectMake(300, 80, 100, 80)];
[btn2 setTitle:@"Press_Two" forState:UIControlStateNormal];
btn2.backgroundColor = [UIColor redColor];
[btn2 addTarget:self action:@selector(action2) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn2];
}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
}
- (void)action1{
Test2ViewController *vc = [[Test2ViewController alloc] init];
[self.navigationController pushViewController:vc animated:NO];
}
- (void)action2{
}
Test2ViewController.m
#import "Test2ViewController.h"
@interface Test2ViewController ()
@end
@implementation Test2ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"Test2ViewController";
self.view.backgroundColor = [UIColor greenColor];
}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
}
@end
修改ViewController.m如下
#import "ViewController.h"
#import "Test1ViewController.h"
@interface ViewController ()
@property (nonatomic,strong) UINavigationController *nav;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Test1ViewController *test1 = [[Test1ViewController alloc] init];
_nav = [[UINavigationController alloc] initWithRootViewController:test1];
[self.view addSubview:_nav.view];
}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
}
@end
解耦步驟:
- 建立AppDelegate+Stastic 類別管理所有需要統(tǒng)計(jì)的類跟方法。
- 實(shí)現(xiàn)一個(gè)解析統(tǒng)計(jì)的類
- 程序啟動(dòng)調(diào)用 [AppDelegate setupLogging];
AppDelegate+Stastic.m
#import "AppDelegate+Stastic.h"
#import "DLAOP.h"
@implementation AppDelegate (Stastic)
+ (void)setupLogging{
NSDictionary *configDic = @{
@"ViewController":@{
@"des":@"show ViewController",
},
@"Test1ViewController":@{
@"des":@"show Test1ViewController",
@"TrackEvents":@[@{
@"EventDes":@"click action1",
@"EventSelectorName":@"action1",
@"block":^(id<AspectInfo>aspectInfo){
NSLog(@"統(tǒng)計(jì) Test1ViewController action1 點(diǎn)擊事件");
},
},
@{
@"EventDes":@"click action2",
@"EventSelectorName":@"action2",
@"block":^(id<AspectInfo>aspectInfo){
NSLog(@"統(tǒng)計(jì) Test1ViewController action2 點(diǎn)擊事件");
},
}],
},
@"Test2ViewController":@{
@"des":@"show Test2ViewController",
}
};
[DLAOP setUpWithConfig:configDic];
}
@end
DLAOP.m
#import "DLAOP.h"
@import UIKit;
typedef void (^AspectHandlerBlock)(id<AspectInfo> aspectInfo);
@implementation DLAOP
+ (void)setUpWithConfig:(NSDictionary *)configDic{
// hook 所有頁(yè)面的viewDidAppear事件
[UIViewController aspect_hookSelector:@selector(viewDidAppear:)
withOptions:AspectPositionAfter
usingBlock:^(id<AspectInfo> aspectInfo){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *className = NSStringFromClass([[aspectInfo instance] class]);
NSString *des = configDic[className][@"des"];
if (des) {
NSLog(@"%@",des);
}
});
} error:NULL];
for (NSString *className in configDic) {
Class clazz = NSClassFromString(className);
NSDictionary *config = configDic[className];
if (config[@"TrackEvents"]) {
for (NSDictionary *event in config[@"TrackEvents"]) {
SEL selekor = NSSelectorFromString(event[@"EventSelectorName"]);
AspectHandlerBlock block = event[@"block"];
[clazz aspect_hookSelector:selekor
withOptions:AspectPositionAfter
usingBlock:^(id<AspectInfo> aspectInfo){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
block(aspectInfo);
});
}error:NULL];
}
}
}
}
@end
程序啟動(dòng)調(diào)用
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[AppDelegate setupLogging];
return YES;
}
具體請(qǐng)下載源碼 Demo