09 | 無侵入的埋點方案如何實現(xiàn)纬凤?

埋點可以解決兩大類問題:一是了解用戶使用App的行為福贞,二是降低分析線上問題的難度。
iOS開發(fā)中常見的埋點方式停士,主要包括代碼埋點挖帘、可視化埋點和無埋點這三種。
運行時方法替換方式進行埋點
在iOS開發(fā)中最常見的三種埋點向瓷,就是對頁面進入次數(shù)、頁面停留時間舰涌、點擊事件的埋點猖任。
具體的實現(xiàn)方法是:先寫一個運行時方法替換的類SMHook,加上替換的方法 hookClass:fromSelector:toSelector瓷耙,代碼如下:

#import "SMHook.h"
#import <objc/runtime.h>

@implementation SMHook

+ (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

這個方法利用運行時 method_exchangeImplementations 接口將方法的實現(xiàn)進行了交換长搀,原方法調(diào)用時就會被 hook 住,從而去執(zhí)行指定的方法鸡典。

面進入次數(shù)源请、頁面停留時間都需要對 UIViewController 生命周期進行埋點,你可以創(chuàng)建一個 UIViewController 的 Category彻况,代碼如下:

@implementation UIViewController (logger)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 通過 @selector 獲得被替換和替換方法的 SEL谁尸,作為 SMHook:hookClass:fromeSelector:toSelector 的參數(shù)傳入 
        SEL fromSelectorAppear = @selector(viewWillAppear:);
        SEL toSelectorAppear = @selector(hook_viewWillAppear:);
        [SMHook hookClass:self fromSelector:fromSelectorAppear toSelector:toSelectorAppear];
        
        SEL fromSelectorDisappear = @selector(viewWillDisappear:);
        SEL toSelectorDisappear = @selector(hook_viewWillDisappear:);
        
        [SMHook 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];
    [self hook_viewWillDisappear:animated];
}

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

那么良蛮,我們要怎么區(qū)別不同的 UIViewController 呢?我一般采取的做法都是悍赢,使用NSStringFromClass([self class]) 方法來取類名决瞳。這樣货徙,我就能夠通過類名來區(qū)別不同的UIViewController了。

對于點擊事件來說皮胡,我們也可以通過運行時方法替換的方式進行無侵入埋點痴颊。這里最主要的工作是,找到這個點擊事件的方法 sendAction:to:forEvent:胸囱,然后在 +load() 方法使用 SMHook 替換成為你定義的方法祷舀。完整代碼實現(xiàn)如下:

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

- (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]);
        [[[SMLogger create] message:[NSString stringWithFormat:@"%@ %@",targetName,actionString]] save];
    }
}

和 UIViewController 生命周期埋點不同的是烹笔,UIButton 在一個視圖類中可能有多個不同的繼承類裳扯,相同 UIButton 的子類在不同視圖類的埋點也要區(qū)別開。所以谤职,我們需要通過 “action 選擇器名 NSStringFromSelector(action)” +“視圖類名 NSStringFromClass([target class])”組合成一個唯一的標識饰豺,來進行埋點記錄。

事件唯一標識
每個子視圖在父視圖中都會有自己的索引允蜈,所以如果我們再加上這個索引的話冤吨,每個視圖的標識就是唯一的了。
UITableViewCell 需要使用 indexPath饶套,這個值里包含了 section 和 row 的值漩蟆。所以,我們可以通過 indexPath 來確定每個 Cell 的唯一性
除了上面提到的這些特殊情況外妓蛮,還有一種情況使得我們也難以得到準確的唯一標識怠李。如果視圖層級在運行時會被更改,比如執(zhí)行 insertSubView:atIndex:蛤克、removeFromSuperView 等方法時捺癞,我們也無法得到唯一標識,即使只截取部分路徑也無法保證后期代碼更新時不會動到這個部分构挤。就算是運行時視圖層級不會修改髓介,以后需求迭代頁面更新頻繁的話,視圖唯一標識也需要同步的更新維護筋现。

這種問題就不好解決了唐础,事件唯一標識的準確性難以保障,這也是通過運行時方法替換進行無侵入埋點很難在各個公司全面鋪開的原因矾飞。雖然無侵入埋點無法覆蓋到所有情況彻犁,全面鋪開面臨挑戰(zhàn),但是無侵入埋點還是解決了大部分的埋點需求凰慈,也節(jié)省了大量的人力成本汞幢。

小結(jié)
今天這篇文章,我與你分享了運行時替換方法進行無侵入埋點的方案微谓。這套方案由于唯一標識難以維護和準確性難以保障的原因森篷,很難被全面采用输钩,一般都只是用于一些功能和視圖穩(wěn)定的地方,手動侵入式埋點方式依然占據(jù)大部分場景仲智。

無侵入埋點也是業(yè)界一大難題买乃,目前還只是初級階段,還有很長的路要走钓辆。我認為剪验,運行時替換方法的方式也只是一種嘗試,但是現(xiàn)實中業(yè)務代碼太過復雜前联。同時功戚,為了使無侵入的埋點能夠覆蓋得更全、準確度更高似嗤,代價往往是對埋點所需的標識維護成本不斷增大啸臀。

所以說,我覺得這種方案并不一定是未來的方向烁落。我倒是覺得使用 Clang AST 的接口乘粒,在構(gòu)建時遍歷 AST,通過定義的規(guī)則將所需要的埋點代碼直接加進去伤塌,可能會更加合適灯萍。這時,我們可以使用前一篇文章“如何利用 Clang 為 App 提質(zhì)每聪?”中提到的 LibTooling 來開發(fā)一個獨立的工具旦棉,專門以靜態(tài)方式插入埋點代碼。這樣做熊痴,既可以享受到手動埋點的精確性他爸,還能夠享受到無侵入埋點方式的統(tǒng)一維護聂宾、開發(fā)解耦果善、易維護的優(yōu)勢。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末系谐,一起剝皮案震驚了整個濱河市巾陕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纪他,老刑警劉巖鄙煤,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異茶袒,居然都是意外死亡梯刚,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門薪寓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來亡资,“玉大人澜共,你說我怎么就攤上這事∽赌澹” “怎么了嗦董?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長瘦黑。 經(jīng)常有香客問我京革,道長,這世上最難降的妖魔是什么幸斥? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任匹摇,我火速辦了婚禮,結(jié)果婚禮上睡毒,老公的妹妹穿的比我還像新娘来惧。我一直安慰自己,他們只是感情好演顾,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布供搀。 她就那樣靜靜地躺著,像睡著了一般钠至。 火紅的嫁衣襯著肌膚如雪葛虐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天棉钧,我揣著相機與錄音盟蚣,去河邊找鬼。 笑死卵酪,一個胖子當著我的面吹牛望众,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播佑钾,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼西疤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了休溶?” 一聲冷哼從身側(cè)響起代赁,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎兽掰,沒想到半個月后芭碍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡孽尽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年窖壕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡瞻讽,死狀恐怖狐蜕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情卸夕,我是刑警寧澤层释,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站快集,受9級特大地震影響贡羔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜个初,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一乖寒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧院溺,春花似錦楣嘁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至谆膳,卻和暖如春叭爱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背漱病。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工买雾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人杨帽。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓漓穿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親注盈。 傳聞我的和親對象是個殘疾皇子晃危,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355