iOS 無侵入埋點(diǎn)的實踐記錄及思考

前言

在初期,沒有做好埋點(diǎn)工作母截,或者著急趕時間到忽,未能合理的做好埋點(diǎn)的工作,隨著用戶的增多微酬,就會有分析用戶的行為需求绘趋,統(tǒng)計某個頁面用戶的留存時間颤陶,雖然市面上有很多統(tǒng)計的SDK,他們大部分都是需要一個頁面一個頁面去添加陷遮,這對于程序猿來說是很不友好的滓走,工作量又大,又不好管理帽馋,突然有一天需要修改某個地方搅方,又要挨個去查找添加的埋點(diǎn)方法,去重新更改一遍绽族。怎么樣才能做好統(tǒng)一管理這些埋點(diǎn)的工作姨涡,讓他們都統(tǒng)一到一塊,又方便管理吧慢,是我們需要思考的涛漂,而且這樣也節(jié)省了大家的時間。

思考

大家都知道objective-c是運(yùn)行時的機(jī)制检诗,所謂運(yùn)行時就是將數(shù)據(jù)類型的確定由編譯期延遲到了運(yùn)行時匈仗,objective-c是通過runtime來實現(xiàn)的,它是一個非常強(qiáng)大的C語言庫 逢慌,這個代碼很早以前就開源了悠轩,想要了解objective-c,可以看看Apple的Github
Apple opensource開源代碼攻泼。我們平時所編寫的objective-c代碼火架,會在運(yùn)行時轉(zhuǎn)換成runtimec語言代碼,objective-c通過runtime創(chuàng)建類跟對象忙菠,并進(jìn)行消息的發(fā)送與轉(zhuǎn)發(fā)何鸡。

在做無侵入埋點(diǎn)的同時,我們需要了解下我們做埋點(diǎn)統(tǒng)計時需要在什么地方進(jìn)行埋點(diǎn)統(tǒng)計只搁。
以下是我的埋點(diǎn)思路


image.png

實踐

我們確定了需要在什么地方進(jìn)行埋點(diǎn)音比,接下來就開始實踐,Show me your code

首先我們寫個工具類用來統(tǒng)計頁面

///后期用到交換方法比較多氢惋,統(tǒng)一一個函數(shù)進(jìn)行方法交換
- (void)ljl_exchangeMethodWithClass:(Class)cls
                        originalSEL:(SEL)originalSEL
                          changeSEL:(SEL)changeSEL{
    Method originalMethod = class_getInstanceMethod(cls, originalSEL);
    Method changeMethod = class_getInstanceMethod(cls, changeSEL);
    method_exchangeImplementations(originalMethod, changeMethod);
}

記錄打印日志統(tǒng)一管理

- (void)recordHookClass:(Class)cls identifier:(NSString *)identifier{
    NSLog(@"當(dāng)前類名:%@",NSStringFromClass(cls));
    NSLog(@"標(biāo)識符:%@",identifier);
    
}

UIViewController統(tǒng)計

+(void)load{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            ///獲取
            SEL willAppear = @selector(viewWillAppear:);
            SEL hook_willAppear = @selector(hook_viewWillAppear:);
            [[LJL_HookObjcLog logManage]ljl_exchangeMethodWithClass:self originalSEL:willAppear changeSEL:hook_willAppear];
            
          
            SEL disappear = @selector(viewDidDisappear:);
            SEL hook_disappear = @selector(hook_viewDidDisappear:);
            [[LJL_HookObjcLog logManage]ljl_exchangeMethodWithClass:self originalSEL:disappear changeSEL:hook_disappear];
            
        });
}

方法實現(xiàn)


- (void)hook_viewWillAppear:(BOOL)animated{
    [[LJL_HookObjcLog logManage] recordHookClass:self.class identifier:@"進(jìn)入"];
    [self hook_viewWillAppear:animated];
}

- (void)hook_viewDidDisappear:(BOOL)animated{
    [[LJL_HookObjcLog logManage] recordHookClass:self.class identifier:@"離開"];
    [self hook_viewDidDisappear:animated];
}

此方案只是針對用戶的停留時間及用戶的進(jìn)入次數(shù),日志打印可按需求來統(tǒng)計稽犁,不同的需求進(jìn)行不同的方式焰望。

UITableView

UITableViewUICollectionView統(tǒng)計用戶點(diǎn)擊cell的方法都是在代理中,我們需要進(jìn)行替換設(shè)置delegate的方法已亥,在熊赖、setDelegate:方法中插入統(tǒng)計的代碼,這里有個小坑虑椎,有的頁面是沒有實現(xiàn)didSelectRowAtIndexPath震鹉,為了使得方法不交換可以判斷下是否實現(xiàn)了didSelectRowAtIndexPath再進(jìn)行統(tǒng)計操作俱笛。

Code

+(void)load{
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        SEL originalSEL = @selector(setDelegate:);
        SEL changeSEL = @selector(hook_setDelegate:);
        [[LJL_HookObjcLog logManage]ljl_exchangeMethodWithClass:self originalSEL:originalSEL changeSEL:changeSEL];
        
    });
  
}

函數(shù)實現(xiàn)

- (void)hook_setDelegate:(id<UITableViewDelegate>)delegate{
        [self hook_setDelegate:delegate];
        Method didSelectmethod = class_getInstanceMethod(delegate.class, @selector(tableView:didSelectRowAtIndexPath:));
        IMP hookIMP = class_getMethodImplementation(self.class, @selector(hook_tableView:didSelectRowAtIndexPath:));
        
        char const* type = method_getTypeEncoding(didSelectmethod);
        class_addMethod(delegate.class, @selector(hook_tableView:didSelectRowAtIndexPath:), hookIMP, type);
        Method hookMethod = class_getInstanceMethod(delegate.class, @selector(hook_tableView:didSelectRowAtIndexPath:));
        method_exchangeImplementations(didSelectmethod, hookMethod);

}

- (void)hook_tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    [[LJL_HookObjcLog logManage] recordHookClass:self.class identifier:[NSString stringWithFormat:@"%ld,%ld",indexPath.row,indexPath.section]];
    [self hook_tableView:tableView didSelectRowAtIndexPath:indexPath];
}

UIButton的點(diǎn)擊事件

+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        ///獲取
        
        SEL originalSEL = @selector(sendAction:to:forEvent:);
        SEL changeSEL = @selector(hook_sendAction:to:forEvent:);
        [[LJL_HookObjcLog logManage]ljl_exchangeMethodWithClass:self originalSEL:originalSEL changeSEL:changeSEL];
    });
}

///MAKR:
- (void)hook_sendAction:(SEL)action
                     to:(nullable id)target
               forEvent:(nullable UIEvent *)event{
    [self hook_sendAction:action to:target forEvent:event];
    ///點(diǎn)擊事件結(jié)束記錄
    if ([[event.allTouches anyObject]phase] == UITouchPhaseEnded) {
        [[LJL_HookObjcLog logManage] recordLogActionHookClass:[target class] action:action identifier:@"UIButton"];
    }
}

UIGestureRecognizer手勢的Hook方法

@implementation UIGestureRecognizer (Log_Category)

+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        ///獲取
        
        SEL originalSEL = @selector(initWithTarget:action:);
        SEL changeSEL = @selector(hook_initWithTarget:action:);
        [[LJL_HookObjcLog logManage]ljl_exchangeMethodWithClass:self originalSEL:originalSEL changeSEL:changeSEL];
    });
}

- (instancetype)hook_initWithTarget:(nullable id)target action:(nullable SEL)action{
    UIGestureRecognizer *gestureRecognizer = [self hook_initWithTarget:target action:action];
    SEL changeSEL = @selector(hook_gestureAction:);
    IMP hookIMP = class_getMethodImplementation(self.class, changeSEL);
    const char *type = method_getTypeEncoding(class_getInstanceMethod([target class], action));
    class_addMethod([target class], changeSEL, hookIMP, type);
    
    [[LJL_HookObjcLog logManage]ljl_exchangeMethodWithClass:[target class] originalSEL:action changeSEL:changeSEL];
    
    
    return gestureRecognizer;
}

- (void)hook_gestureAction:(id)sender{
    [self hook_gestureAction:sender];
    [[LJL_HookObjcLog logManage] recordLogActionHookClass:[sender class] action:@selector(action) identifier:@"手勢"];

}

@end


思考總結(jié)

本文簡單講述無侵入埋點(diǎn)的統(tǒng)計方案,思路大致上是通過Runtime的運(yùn)行機(jī)制传趾,在運(yùn)行期可以向類中新增或替換選擇子所對應(yīng)的方法實現(xiàn)迎膜。使用另外一份實現(xiàn)原有的方法實現(xiàn)。
在無侵入的基礎(chǔ)上浆兰,即降低了代碼的耦合磕仅,又方便了后期維護(hù)管理,相對于可視化埋點(diǎn)簸呈,方便簡單榕订,所有方式都會有優(yōu)點(diǎn)與缺點(diǎn)。
本文描述的優(yōu)點(diǎn)就是無侵入蜕便,低耦合劫恒,好管理維護(hù)。
缺點(diǎn):有些頁面是復(fù)用機(jī)制轿腺,比如cell的復(fù)用两嘴,一個控制器可能多次進(jìn)入,需要我們做好統(tǒng)一管理的標(biāo)識符吃溅,一個button的點(diǎn)擊需要遞歸獲取當(dāng)前的控制器等操作溶诞。有些模塊可能會出現(xiàn)統(tǒng)計不準(zhǔn)確等因素,還有可能團(tuán)隊人員多了决侈,定義的方法有時候都是一致的螺垢,這樣對于這種無侵入的方式最終的效果是不太準(zhǔn)確的。相比較可視化埋點(diǎn)赖歌,數(shù)據(jù)統(tǒng)計的更加合理枉圃,準(zhǔn)確,維護(hù)成本略高
每個項目所要統(tǒng)計的內(nèi)容不一致庐冯,精確的程度也不一樣孽亲,都是各自的觀點(diǎn),本文只是自己的理解與記憶展父,如果你又什么更好的方案可以留言分享返劲,謝謝。

可參考鏈接
有貨 iOS 數(shù)據(jù)非侵入式自動采集探索實踐
網(wǎng)易HubbleData無埋點(diǎn)SDK在iOS端的設(shè)計與實現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末栖茉,一起剝皮案震驚了整個濱河市篮绿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吕漂,老刑警劉巖亲配,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡吼虎,警方通過查閱死者的電腦和手機(jī)犬钢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來思灰,“玉大人玷犹,你說我怎么就攤上這事」俦玻” “怎么了箱舞?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拳亿。 經(jīng)常有香客問我晴股,道長,這世上最難降的妖魔是什么肺魁? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任电湘,我火速辦了婚禮,結(jié)果婚禮上鹅经,老公的妹妹穿的比我還像新娘寂呛。我一直安慰自己,他們只是感情好瘾晃,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布贷痪。 她就那樣靜靜地躺著,像睡著了一般蹦误。 火紅的嫁衣襯著肌膚如雪劫拢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天强胰,我揣著相機(jī)與錄音舱沧,去河邊找鬼。 笑死偶洋,一個胖子當(dāng)著我的面吹牛熟吏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播玄窝,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼牵寺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了恩脂?” 一聲冷哼從身側(cè)響起缸剪,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎东亦,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡典阵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年奋渔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片壮啊。...
    茶點(diǎn)故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡嫉鲸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出歹啼,到底是詐尸還是另有隱情玄渗,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布狸眼,位于F島的核電站藤树,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏拓萌。R本人自食惡果不足惜岁钓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望微王。 院中可真熱鬧屡限,春花似錦、人聲如沸炕倘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽罩旋。三九已至啊央,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瘸恼,已是汗流浹背劣挫。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留东帅,地道東北人压固。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像靠闭,于是被迫代替她去往敵國和親帐我。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評論 2 353

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉愧膀,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,709評論 0 9
  • 浩瀚而神秘的互加太空中檩淋,有這樣一顆耀眼的星星:它用聲音裝點(diǎn)校園芬为,它用旋律吸引孩子萄金,它用多情打動心靈。它就是《快樂音...
    寧都2021楊春美閱讀 656評論 1 13
  • 以37歲英年謝世的張蔭麟,用半部《中國史綱》在士林贏得盛譽(yù)询张∷锕裕“張蔭麟先生,史學(xué)家也份氧,亦哲學(xué)家也唯袄。其宏博之思,蘊(yùn)諸中...
    吳玫閱讀 651評論 0 2
  • 佟大為和王一博將這些道理都恰到好處的演繹了出來蜗帜,由儉入奢易恋拷,由奢入儉難;每一個職位都不分貴賤钮糖,只是看你是否有足夠的...