埋點方案學習

解決什么問題?

  1. 了解用戶使用App的行為
  2. 降低分析線上問題的難度

常見埋點方式?

  1. 代碼埋點
  2. 可視化埋點
  3. 無埋點
三者對比
類型 實現(xiàn)形式 優(yōu)點 缺點 是否侵入
代碼埋點 在需要埋點的代碼上增加埋點代碼 方便記錄當前環(huán)境變量值,方便調(diào)試,跟蹤埋點內(nèi)容 開發(fā)工作量大,代碼到處散布,難以維護
可視化埋點 埋點增加和修改的工作可視化 提升了增加和修改埋點的體驗 N/A
無埋點 全埋點 埋點代碼不會出現(xiàn)在業(yè)務代碼中,容易管理和維護 埋點成本高,后期解析負責,view_path的不確定性

小結:使用無侵入埋點方案,既可以做到埋點被統(tǒng)一維護,又可以實現(xiàn)和工程代碼的解耦

無侵入埋點方案實現(xiàn)?

運行時方法替換方式進行埋點?
常見三者埋點
  • 頁面進入次數(shù)
  • 頁面停留時間
  • 點擊事件

思路:對于上述三種常見的埋點,可以通過運行時方法替換技術來插入埋點代碼。
實現(xiàn):先寫一個運行時方法替換的類SMHook,加上替換的方法hookClass:fromSelector:toSelector

#import "JZYHook.h"

#import <objc/runtime.h>

@implementation JZYHook

+ (void)hookClass:(Class)classObject fromSelector:(SEL)fromSelector toSelector:(SEL)toSelector {
    
    Class class = classObject;
    // 得到被替換類的實例方法
    Method fromMethod = class_getInstanceMethod(class, fromSelector);
    // 得到替換類的實例方法
    Method toMethod = class_getInstanceMethod(class, toSelector);
    
    // class_addMethod 返回成功表示被替換的方法沒實現(xiàn),然后會通過 class_addMethod 方法先實現(xiàn),然后進行替換;返回失敗則表示被替換方法已存在,可以直接進行 IMP 指針交換
    if (class_addMethod(class, fromSelector, method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
        
        // 進行方法替換
        class_replaceMethod(class, toSelector, method_getImplementation(fromMethod), method_getTypeEncoding(fromMethod));
    }
    else {
        
        // 交換 IMP 指針
        method_exchangeImplementations(fromMethod, toMethod);
    }
}

@end

頁面進入次數(shù)、頁面停留時間都需要對UIViewController生命周期進行埋點

#import "JZYHook.h"

#import "JZYLogger.h"

@implementation UIViewController (JZYLogger)

+ (void)load {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        // 通過 @selector 獲得被替換和替換方法的 SEL,作為 JZYHook: hookClass:fromSelector:toSelector 的參數(shù)傳入
        SEL fromSelectorAppear = @selector(viewWillAppear:);
        SEL toSelectorAppear = @selector(hook_viewWillAppear:);
        [JZYHook hookClass:self fromSelector:fromSelectorAppear toSelector:toSelectorAppear];
        
        SEL fromSelectorDisappear = @selector(viewWillDisappear:);
        SEL toSelectorDisappear = @selector(hook_viewWillDisappear:);
        [JZYHook hookClass:self fromSelector:fromSelectorDisappear toSelector:toSelectorDisappear];
    });
}

- (void)hook_viewWillAppear:(BOOL)animated {
    
    // 先執(zhí)行插入代碼,再執(zhí)行原viewWillAppear方法
    [self insertToViewWillAppear];
    [self hook_viewWillAppear:animated];
}

- (void)hook_viewWillDisappear:(BOOL)animated {
    
    // 先執(zhí)行插入代碼,再執(zhí)行原viewWillDisappear方法
    [self insertToViewWillDisappear];
}

- (void)insertToViewWillAppear {
    
    // 在viewWillAppear 時進行日志的埋點
    [[[JZYLogger create] message:[NSString stringWithFormat:@"%@ Appear", NSStringFromClass([self class])] classify:@"ProjectClassifyOperation"] save];
}

- (void)insertToViewWillDisappear {
    
    // 在viewWillDisappear 時進行日志的埋點
    [[[JZYLogger create] message:[NSString stringWithFormat:@"%@ Disappear", NSStringFromClass([self class])] classify:@"ProjectClassifyOperation"] save];
}

@end

點擊事件,通過運行時方法替換的方式進行埋點

#import "JZYHook.h"
#import "JZYLogger.h"

@implementation UIButton (JZYLogger)

+ (void)load {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        // 通過 @selector 獲得被替換和替換方法的 SEL,作為 JZYHook: hookClass:fromSelector:toSelector 的參數(shù)傳入
        SEL fromSelectorAppear = @selector(sendAction:to:forEvent:);
        SEL toSelectorAppear = @selector(hook_sendAction:to:forEvent:);
        [JZYHook hookClass:self fromSelector:fromSelectorAppear toSelector:toSelectorAppear];
    });
}

- (void)hook_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    
    [self insertToSendAction:action to:target forEvent:event];
    [self hook_sendAction:action to:target forEvent:event];
}

- (void)insertToSendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    
    // 日志記錄
    if ([[[event allTouches] anyObject] phase] == UITouchPhaseEnded) {
        
        NSString *actionString = NSStringFromSelector(action);
        NSString *targetName = NSStringFromClass([target class]);
        [[[JZYLogger create] message:[NSString stringWithFormat:@"%@ %@", targetName, actionString] classify:@"ProjectClassifyOperation"] save];
    }
}

@end

如何區(qū)分相同類在不同視圖樹節(jié)點的情況?
通過一個唯一標識來區(qū)分不同的事件。
思路:想辦法找出元素間不相同的因素然后進行組合,最后形成一個能區(qū)別于其他元素的標識來。
比如:

  • 視圖層級的路徑+索引
  • UITableViewCell 使用 indexPath
  • UIAlertController 使用內(nèi)容確定

總結:由于唯一標識難以維護和準確性難以保障的原因,很難被全面采用,一般只用于一些功能和視圖穩(wěn)定的地方,手動侵入式埋點方式依然占據(jù)大部分場景。

推薦使用 Clang AST

參考

無侵入的埋點方案如何實現(xiàn)
iOS無痕埋點方案分享探究

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

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

  • 前言 最近跟同事花了點時間來思考可視化埋點,并沒有什么突破性的進展,不過市面上很多關于可視化埋點的技術文章都在講達...
    daixunry閱讀 8,243評論 1 38
  • https://mp.weixin.qq.com/s/u-HmmrSAgtER1N2pKxCm0A 隨著公司業(yè)務的...
    海浪萌物閱讀 3,150評論 1 1
  • 在iOS項目開發(fā)中,我們要收集用戶的行為信息以便對項目進行分析統(tǒng)計,就需要在代碼中進行埋點統(tǒng)計。 一、通常的埋點方...
    大刀闊斧007閱讀 1,645評論 1 3
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,658評論 1 32
  • 〖生活并不需要這么些無謂的執(zhí)著,學會放棄,生活就真的容易。〗 人生就是這樣,得失無常,凡是路過的,都算風景;能...
    幸運伴隨我閱讀 330評論 0 0

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