iOS 無侵入埋點方案探索

GitHub項目地址

前言

最近業(yè)務(wù)需要加入一大批埋點統(tǒng)計事件,這個頁面添加一點代碼那個頁面添加一點代碼,各個頁面內(nèi)耦合了大量的無關(guān)業(yè)務(wù)的埋點代碼使得頁面雜亂不堪,所以想尋找一個比較好的方法來解決這個事情。

探索

經(jīng)過一番考慮想到如下方案:
1、每個業(yè)務(wù)頁面添加一個埋點類,單獨將埋點的方法提取到這個類中。
2、利用runtime在底層進行方法攔截,從而添加埋點代碼。

最后采用了第2種方案。

技術(shù)原理

一、Method-Swizzling

oc中的方法調(diào)用其實是向一個對象發(fā)送消息 ,利用oc的動態(tài)性可以實現(xiàn)方法的交換。
1、用 method_exchangeImplementations 方法來交換2個方法中的IMP
2、用 class_replaceMethod 方法來替換類的方法,
3、用 method_setImplementation 方法來直接設(shè)置某個方法的IMP

二、Target-Action

按鈕的點擊事件,UIControl會調(diào)用sendAction:to:forEvent:來將行為消息轉(zhuǎn)發(fā)到UIApplication,再由UIApplication調(diào)用其sendAction:to:fromSender:forEvent:方法來將消息分發(fā)到指定的target上。

分析及實現(xiàn)

一、 需要添加埋點統(tǒng)計的地方:

1、button相關(guān)的點擊事件
2、頁面進入、頁面推出
3、tableView的點擊
4、collectionView的點擊
5、手勢相關(guān)事件

二、分析

1、對于用戶交互的操作,我們使用runtime 對應(yīng)的方法hook 下sendAction:to:forEvent:便可以得到進行的交互操作。
這個方法對UIControl及繼承UIControl的子類對象有效,如:UIButton、UISlider等。
2、對于UIViewController,hook下ViewDidAppear:這個方法知道哪個頁面顯示了就足夠了。
3、對于tableview及collectionview,我們hook下setDelegate:方法。檢測其有沒有實現(xiàn)對應(yīng)的點擊代理,因為tableView:didSelectRowAtIndexPath:及collectionView:didSelectItemAtIndexPath:是option的不是必須要實現(xiàn)的。
4、對于手勢,我們在創(chuàng)建的時候進行hook,方法為initWithTarget:action:。

三、代碼實現(xiàn)

1、UIControl+Track

@implementation UIControl (Track)

+ (void)initialize {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL originalSelector = @selector(sendAction:to:forEvent:);
        SEL swizzingSelector = @selector(dk_sendAction:to:forEvent:);
        [DKMethodSwizzingTool swizzingForClass:[self class] originalSel:originalSelector swizzingSel:swizzingSelector];
    });
}

- (void)dk_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    [self dk_sendAction:action to:target forEvent:event];
    
    //埋點實現(xiàn)區(qū)域====

}

@end

2、UIViewController+Track

+ (void)initialize {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL originalDidLoadSel = @selector(viewDidLoad);
        SEL swizzingDidLoadSel = @selector(dk_viewDidLoad);
        [DKMethodSwizzingTool swizzingForClass:[self class] originalSel:originalDidLoadSel swizzingSel:swizzingDidLoadSel];
    });
}

- (void)dk_viewDidLoad {
    [self dk_viewDidLoad];
    
    //埋點實現(xiàn)區(qū)域====
  
}

3、UITableView+Track

@implementation UITableView (Track)
+ (void)initialize {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL originalSelector = @selector(setDelegate:);
        SEL swizzingSelector = @selector(dk_setDelegate:);
        [DKMethodSwizzingTool swizzingForClass:[self class] originalSel:originalSelector swizzingSel:swizzingSelector];
    });
}

- (void)dk_setDelegate:(id<UITableViewDelegate>)delegate {
    [self dk_setDelegate:delegate];
    
    SEL originalSel = @selector(tableView:didSelectRowAtIndexPath:);
    SEL swizzingSel = NSSelectorFromString([NSString stringWithFormat:@"%@/%@", NSStringFromClass([delegate class]),@(self.tag)]);
    
    //didSelectRowAtIndexPath不一定要實現(xiàn),未實現(xiàn)在跳過
    if (![DKMethodSwizzingTool isContainSel:originalSel class:[delegate class]]) {
        return;
    }
    
    BOOL addMethod = class_addMethod([delegate class], swizzingSel, method_getImplementation(class_getInstanceMethod([self class], @selector(dk_tableView:didSelectRowAtIndexPath:))), nil);
    if (addMethod) {
        Method originalMetod = class_getInstanceMethod([delegate class], originalSel);
        Method swizzingMethod = class_getInstanceMethod([delegate class], swizzingSel);
        method_exchangeImplementations(originalMetod, swizzingMethod);
    }
}

- (void)dk_tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSString *identifier = [NSString stringWithFormat:@"%@/%@", NSStringFromClass([self class]),@(tableView.tag)];
    SEL sel = NSSelectorFromString(identifier);
    if ([self respondsToSelector:sel]) {
        IMP imp = [self methodForSelector:sel];
        void (*func)(id, SEL,id,id) = (void *)imp;
        func(self, sel,tableView,indexPath);
    }
    
    //埋點實現(xiàn)區(qū)域====
}

4、UICollectionView+Track同時拓展
5、UIGestureRecognizer+Track

結(jié)果

2019-05-07 15:29:57.725041+0800 DKDataTrackKitDemo[18913:1357822] eventName:button----eventParam:{
    content = dictionary;
    text = hahha;
    tips = test;
}
2019-05-07 15:29:57.735695+0800 DKDataTrackKitDemo[18913:1357822] eventName:ViewController----eventParam:{
}
2019-05-07 15:29:59.830922+0800 DKDataTrackKitDemo[18913:1357822] eventName:tableView----eventParam:{
    text = tableview;
}
2019-05-07 15:30:01.178838+0800 DKDataTrackKitDemo[18913:1357822] eventName:collectionview----eventParam:{
    text = collectionView;
}

規(guī)則

其中用到的plist生成規(guī)則:

1、Action:

對應(yīng)的是UIControl。
每一個Action統(tǒng)計事件的匹配規(guī)則:頁面名稱/方法名/tag
參數(shù):EventName事件名、EventParam事件對應(yīng)的參數(shù)

屏幕快照 2019-05-07 下午4.34.05.png

2、TableView

對應(yīng)的是UITableView。
每一個TableView統(tǒng)計事件的匹配規(guī)則:頁面名稱/tag
參數(shù):viewcontroller是否從viewcontroller中取參數(shù)、 EventName事件名、EventParam事件對應(yīng)的參數(shù)

屏幕快照 2019-05-07 下午4.39.48.png

3、UICollectionView

規(guī)則同上。

4、UIGestureRecognizer

對應(yīng)的是手勢UIGestureRecognizer。
每一個UIGestureRecognizer統(tǒng)計事件的匹配規(guī)則:頁面名稱/方法名
參數(shù):EventName事件名、EventParam事件對應(yīng)的參數(shù)

屏幕快照 2019-05-07 下午4.45.02.png
5、UIViewController

規(guī)則同上。

寫在最后

hook方式非常強大,幾乎可以攔截你想要的全部方法,但是每次觸發(fā)hook必然會置換IMP的整個過程,頻繁的置換會造成資源的消耗,不到萬不得已,建議少用。

GitHub項目地址

參考感謝:
https://blog.csdn.net/SandyLoo/article/details/81202105

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 簡單介紹一下 AOP 無痕埋點最重要的技術(shù)是將埋點代碼從業(yè)務(wù)代碼中剝離,放到獨立的模塊中的技術(shù)。寫業(yè)務(wù)的同學(xué)只需按...
    Magic_Unique閱讀 7,998評論 16 53
  • 轉(zhuǎn)載: https://blog.csdn.net/qq871531334/article/details/822...
    NicooYang閱讀 1,608評論 0 9
  • 在iOS開發(fā)中經(jīng)常會涉及到觸摸事件。本想自己總結(jié)一下,但是遇到了這篇文章,感覺總結(jié)的已經(jīng)很到位,特此轉(zhuǎn)載。作者:L...
    WQ_UESTC閱讀 6,106評論 4 26
  • 本文主要講解iOS觸摸事件的一系列機制,涉及的問題大致包括: 觸摸事件由觸屏生成后如何傳遞到當(dāng)前應(yīng)用? 應(yīng)用接收觸...
    baihualinxin閱讀 1,225評論 0 9
  • 前言 隨著公司業(yè)務(wù)的發(fā)展,數(shù)據(jù)的重要性日益體現(xiàn)出來。 數(shù)據(jù)埋點的準(zhǔn)確和全面性顯得尤為重要。通過精準(zhǔn)和詳細的數(shù)據(jù),后...
    MMR無與倫比閱讀 5,961評論 2 13