參考博客:http://www.itdecent.cn/p/c783fb20a905
AOP(Aspect-Oriented Programming):面向切面的編程。OOP(Object-Oriented Programming)面向?qū)ο蟮木幊?。?duì)于OOP我們已經(jīng)再熟悉不過(guò)了,對(duì)于AOP,可能我們會(huì)覺(jué)得是一種新特性,其實(shí)AOP是對(duì)OOP的一種補(bǔ)充,OOP面向的是縱向編程,繼承、封裝、多態(tài)是其三大特性,而AOP是面向橫向的編程。主要用來(lái)處理一些具有橫切性質(zhì)的系統(tǒng)性服務(wù),如日志記錄、權(quán)限管理、緩存、對(duì)象池管理,數(shù)據(jù)統(tǒng)計(jì)等,AOP 已經(jīng)成為一種非常常用的解決方案。
在iOS中AOP也是可以發(fā)揮出很大實(shí)用性。因?yàn)镺C是一門(mén)動(dòng)態(tài)語(yǔ)言,使用起來(lái)十分方便。在 Objective-C中,我們可以使用AOP在運(yùn)行時(shí)增加適合的代碼,而不破壞原有的代碼結(jié)構(gòu)和業(yè)務(wù)。比如:
- 在類的特定方法調(diào)用前運(yùn)行特定的代碼
- 在類的特定方法調(diào)用后運(yùn)行特定的代碼
- 增加代碼來(lái)替代原來(lái)的類的方法的實(shí)現(xiàn)

要在iOS中實(shí)現(xiàn)AOP最簡(jiǎn)單便捷的方法無(wú)疑是使用Aspects了,Aspects是一個(gè)輕量級(jí)的面向切面編程的庫(kù)。它主要提供了三個(gè)切入點(diǎn):before(在原始的方法前執(zhí)行)/instead(替換原始的方法執(zhí)行)/after(在原始的方法后執(zhí)行,默認(rèn)),通過(guò)Runtime消息轉(zhuǎn)發(fā)實(shí)現(xiàn)Hook,同時(shí)這也會(huì)帶來(lái)一定的負(fù)擔(dān),所以它不適合循環(huán)多次調(diào)用的方法。
說(shuō)它是輕量級(jí)的一點(diǎn)也不為過(guò),Aspects只包括兩個(gè)方法:一個(gè)類方法,一個(gè)實(shí)例方法。
+ (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;
函數(shù)使用方式簡(jiǎn)單易懂,掛鉤的方式為三種:
typedef NS_OPTIONS(NSUInteger, AspectOptions) {
AspectPositionAfter = 0, /// 在原始方法后調(diào)用(默認(rèn))
AspectPositionInstead = 1, /// 替換原始方法
AspectPositionBefore = 2, /// 在原始方法前調(diào)用
AspectOptionAutomaticRemoval = 1 << 3 /// 在執(zhí)行1次后自動(dòng)移除
};
使用示例:
首先新建一個(gè)Person類,實(shí)現(xiàn)一個(gè)實(shí)例方法和類方法。
- (void)instanceMethod {
NSLog(@"實(shí)例方法");
}
+ (void)classMethod {
NSLog(@"類方法");
}
hook實(shí)例方法
學(xué)過(guò)OC類的底層原理的同學(xué)肯定知道實(shí)例方法存儲(chǔ)在類對(duì)象中,類方法存儲(chǔ)在元類對(duì)象中,如果不知道可以看這篇文章:http://www.itdecent.cn/p/4ed89787f38d
因?yàn)閷?shí)例方法都存儲(chǔ)在類中,所以我們對(duì)Person類做hook操作就可以。
[Person aspect_hookSelector:@selector(instanceMethod) withOptions:AspectPositionBefore usingBlock:^(void) {
// instanceMethod方法調(diào)用前執(zhí)行
} error:nil];
hook類方法
因?yàn)閷?shí)例方法是存儲(chǔ)在類中的,而類方法存儲(chǔ)在元類中。所以hook類方法需要導(dǎo)入#import <objc/runtime.h>運(yùn)行時(shí)框架獲取到Person類的元類。
objc_getMetaClass和object_getClass方法都可以獲取到元類,選擇一個(gè)調(diào)用即可。
// Class class = objc_getMetaClass(@"Person".UTF8String);
Class class = object_getClass(Person.class);
[class aspect_hookSelector:@selector(classMethod) withOptions:AspectPositionBefore usingBlock:^(void) {
// classMethod方法調(diào)用前執(zhí)行
} error:nil];
對(duì)于有參數(shù)的方法,如果需要在回調(diào)里用到,可以在block回調(diào)里,主動(dòng)在第一個(gè)固定參數(shù)id<AspectInfo> aspectInfo后面加上對(duì)應(yīng)類型的參數(shù)。
例如:
[UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
NSLog(@"View Controller %@ will appear animated: %tu", aspectInfo.instance, animated);
} error:NULL];
下面我通過(guò)在醫(yī)生端新增TD統(tǒng)計(jì)的功能需求進(jìn)行實(shí)踐。需要監(jiān)聽(tīng)不同類,不同按鈕,系統(tǒng)方法,及表單元點(diǎn)擊事件。
在醫(yī)生端我是新增了一個(gè)類,YDYStatisticalAnalysisManager專門(mén)用于管理統(tǒng)計(jì)
代碼。
1、頁(yè)面使用時(shí)長(zhǎng)統(tǒng)計(jì),這個(gè)比較簡(jiǎn)單,只需要在Controller頁(yè)面中監(jiān)聽(tīng)viewDidAppear和viewWillDisappear方法的執(zhí)行情況就行了。代碼如下:
#pragma mark -- 監(jiān)控統(tǒng)計(jì)用戶進(jìn)入此界面的時(shí)長(zhǎng),頻率等信息
+ (void)trackViewAppear{
[UIViewController aspect_hookSelector:@selector(viewDidAppear:)
withOptions:AspectPositionBefore
usingBlock:^(id<AspectInfo> info){
//用戶統(tǒng)計(jì)代碼寫(xiě)在此處
UIViewController *currentVC = (UIViewController *)info.instance;
[TalkingData trackPageBegin:currentVC.title];
#ifdef DEBUG
NSLog(@"VC: %@ -- %@---title:%@", NSStringFromClass([info.instance class]), @"viewDidAppear",currentVC.title);
#endif
}
error:NULL];
[UIViewController aspect_hookSelector:@selector(viewWillDisappear:)
withOptions:AspectPositionBefore
usingBlock:^(id<AspectInfo> info){
//用戶統(tǒng)計(jì)代碼寫(xiě)在此處
UIViewController *currentVC = (UIViewController *)info.instance;
[TalkingData trackPageEnd:currentVC.title];
#ifdef DEBUG
NSLog(@"VC: %@ -- %@---title:%@", NSStringFromClass([info.instance class]), @"viewWillDisappear",currentVC.title);
#endif
}
error:NULL];
}
2、按鈕點(diǎn)擊事件,這個(gè)稍微復(fù)雜一點(diǎn)。我們可以新建一個(gè)plist文件來(lái)保存需要統(tǒng)計(jì)的點(diǎn)擊事件方法和其所在的Controller。如:

+ (void)trackParameterEventWithClass:(Class)klass selector:(SEL)selector eventID:(NSString*)eventID{
[klass aspect_hookSelector:selector withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo) {
NSLog(@"統(tǒng)計(jì)事件參數(shù)個(gè)數(shù):%lu",(unsigned long)[aspectInfo arguments].count);
NSLog(@"統(tǒng)計(jì)事件參數(shù)---->%@",[aspectInfo arguments]);
NSString *className = NSStringFromClass([aspectInfo.instance class]);
NSLog(@"統(tǒng)計(jì)事件所屬類名className--->%@",className);
NSLog(@"統(tǒng)計(jì)事件名稱event----->%@",eventID);
if ([aspectInfo arguments].count == 0) {//無(wú)參數(shù)
[TalkingData trackEvent:eventID];
}else{//有參數(shù)
//我們可以通過(guò)事件參數(shù)的值和類名來(lái)進(jìn)行自定義操作
}
}]
以上是目前所用的AOP編程中的實(shí)踐,當(dāng)后續(xù)需要更改統(tǒng)計(jì)數(shù)據(jù)時(shí)只需要改動(dòng)plist文件和這個(gè)YDYStatisticalAnalysisManager類就行了,不會(huì)破壞原有的代碼結(jié)構(gòu),減輕后續(xù)代碼的維護(hù)工作。