iOS 下的AOP編程之打點(diǎn)統(tǒng)計(jì)

概念

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

  1. 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:

  2. 先嘗試添加原 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)給交換掉懂酱。

  3. 類別里添加 +load: 方法,然后在 +load: 里把 viewDidAppear 給替換掉:

  4. 一般情況下誊抛,類別里的方法會(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

解耦步驟:

  1. 建立AppDelegate+Stastic 類別管理所有需要統(tǒng)計(jì)的類跟方法。
  2. 實(shí)現(xiàn)一個(gè)解析統(tǒng)計(jì)的類
  3. 程序啟動(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末癌瘾,一起剝皮案震驚了整個(gè)濱河市觅丰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌妨退,老刑警劉巖妇萄,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異咬荷,居然都是意外死亡冠句,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門幸乒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)懦底,“玉大人,你說(shuō)我怎么就攤上這事罕扎【厶疲” “怎么了?”我有些...
    開封第一講書人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵腔召,是天一觀的道長(zhǎng)杆查。 經(jīng)常有香客問(wèn)我,道長(zhǎng)臀蛛,這世上最難降的妖魔是什么亲桦? 我笑而不...
    開封第一講書人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任崖蜜,我火速辦了婚禮,結(jié)果婚禮上客峭,老公的妹妹穿的比我還像新娘豫领。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绵咱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鼠锈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評(píng)論 1 283
  • 那天星著,我揣著相機(jī)與錄音,去河邊找鬼粗悯。 笑死虚循,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的样傍。 我是一名探鬼主播横缔,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼衫哥!你這毒婦竟也來(lái)了茎刚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤撤逢,失蹤者是張志新(化名)和其女友劉穎膛锭,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蚊荣,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡初狰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了互例。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奢入。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖媳叨,靈堂內(nèi)的尸體忽然破棺而出腥光,到底是詐尸還是另有隱情,我是刑警寧澤糊秆,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布武福,位于F島的核電站,受9級(jí)特大地震影響扩然,放射性物質(zhì)發(fā)生泄漏艘儒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望界睁。 院中可真熱鬧觉增,春花似錦、人聲如沸翻斟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)访惜。三九已至嘹履,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間债热,已是汗流浹背砾嫉。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留窒篱,地道東北人焕刮。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像墙杯,于是被迫代替她去往敵國(guó)和親配并。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容