iOS 無(wú)侵入埋點(diǎn)方案探索

GitHub項(xiàng)目地址

前言

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

探索

經(jīng)過(guò)一番考慮想到如下方案:
1眉厨、每個(gè)業(yè)務(wù)頁(yè)面添加一個(gè)埋點(diǎn)類(lèi)喊式,單獨(dú)將埋點(diǎn)的方法提取到這個(gè)類(lèi)中孵户。
2、利用runtime在底層進(jìn)行方法攔截岔留,從而添加埋點(diǎn)代碼夏哭。

最后采用了第2種方案。

技術(shù)原理

一献联、Method-Swizzling

oc中的方法調(diào)用其實(shí)是向一個(gè)對(duì)象發(fā)送消息 竖配,利用oc的動(dòng)態(tài)性可以實(shí)現(xiàn)方法的交換何址。
1、用 method_exchangeImplementations 方法來(lái)交換2個(gè)方法中的IMP
2进胯、用 class_replaceMethod 方法來(lái)替換類(lèi)的方法用爪,
3、用 method_setImplementation 方法來(lái)直接設(shè)置某個(gè)方法的IMP

二胁镐、Target-Action

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

分析及實(shí)現(xiàn)

一颇玷、 需要添加埋點(diǎn)統(tǒng)計(jì)的地方:

1、button相關(guān)的點(diǎn)擊事件
2就缆、頁(yè)面進(jìn)入帖渠、頁(yè)面推出
3、tableView的點(diǎn)擊
4竭宰、collectionView的點(diǎn)擊
5阿弃、手勢(shì)相關(guān)事件

二、分析

1羞延、對(duì)于用戶交互的操作渣淳,我們使用runtime 對(duì)應(yīng)的方法hook 下sendAction:to:forEvent:便可以得到進(jìn)行的交互操作。
這個(gè)方法對(duì)UIControl及繼承UIControl的子類(lèi)對(duì)象有效伴箩,如:UIButton入愧、UISlider等。
2嗤谚、對(duì)于UIViewController棺蛛,hook下ViewDidAppear:這個(gè)方法知道哪個(gè)頁(yè)面顯示了就足夠了。
3巩步、對(duì)于tableview及collectionview旁赊,我們hook下setDelegate:方法。檢測(cè)其有沒(méi)有實(shí)現(xiàn)對(duì)應(yīng)的點(diǎn)擊代理椅野,因?yàn)閠ableView:didSelectRowAtIndexPath:及collectionView:didSelectItemAtIndexPath:是option的不是必須要實(shí)現(xiàn)的终畅。
4、對(duì)于手勢(shì)竟闪,我們?cè)趧?chuàng)建的時(shí)候進(jìn)行hook离福,方法為initWithTarget:action:。

三炼蛤、代碼實(shí)現(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];
    
    //埋點(diǎn)實(shí)現(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];
    
    //埋點(diǎn)實(shí)現(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不一定要實(shí)現(xiàn)絮识,未實(shí)現(xiàn)在跳過(guò)
    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);
    }
    
    //埋點(diǎn)實(shí)現(xiàn)區(qū)域====
}

4绿聘、UICollectionView+Track同時(shí)拓展
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:

對(duì)應(yīng)的是UIControl斜友。
每一個(gè)Action統(tǒng)計(jì)事件的匹配規(guī)則:頁(yè)面名稱(chēng)/方法名/tag
參數(shù):EventName事件名鳞尔、EventParam事件對(duì)應(yīng)的參數(shù)

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

2懦底、TableView

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

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

3、UICollectionView

規(guī)則同上惯殊。

4酱吝、UIGestureRecognizer

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

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

規(guī)則同上。

寫(xiě)在最后

hook方式非常強(qiáng)大己儒,幾乎可以攔截你想要的全部方法崎岂,但是每次觸發(fā)hook必然會(huì)置換IMP的整個(gè)過(guò)程,頻繁的置換會(huì)造成資源的消耗闪湾,不到萬(wàn)不得已冲甘,建議少用。

GitHub項(xiàng)目地址

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末途样,一起剝皮案震驚了整個(gè)濱河市江醇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌何暇,老刑警劉巖陶夜,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異裆站,居然都是意外死亡条辟,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)宏胯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)捂贿,“玉大人,你說(shuō)我怎么就攤上這事胳嘲〕” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵了牛,是天一觀的道長(zhǎng)颜屠。 經(jīng)常有香客問(wèn)我辰妙,道長(zhǎng),這世上最難降的妖魔是什么甫窟? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任密浑,我火速辦了婚禮,結(jié)果婚禮上粗井,老公的妹妹穿的比我還像新娘尔破。我一直安慰自己,他們只是感情好浇衬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布懒构。 她就那樣靜靜地躺著,像睡著了一般耘擂。 火紅的嫁衣襯著肌膚如雪胆剧。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天醉冤,我揣著相機(jī)與錄音秩霍,去河邊找鬼。 笑死蚁阳,一個(gè)胖子當(dāng)著我的面吹牛铃绒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播螺捐,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼匿垄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了归粉?” 一聲冷哼從身側(cè)響起椿疗,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎糠悼,沒(méi)想到半個(gè)月后届榄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡倔喂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年铝条,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片席噩。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡班缰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出悼枢,到底是詐尸還是另有隱情埠忘,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站莹妒,受9級(jí)特大地震影響名船,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜旨怠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一渠驼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鉴腻,春花似錦迷扇、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至倦青,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盹舞,已是汗流浹背产镐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留踢步,地道東北人癣亚。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像获印,于是被迫代替她去往敵國(guó)和親述雾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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

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