iOS開(kāi)發(fā)使用aspects框架,面向切面編程

參考博客: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)
image

要在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。如:

image
+ (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ù)工作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容