iOS 統(tǒng)計埋點方案探索解析與選擇

前言

統(tǒng)計埋點,作為應(yīng)用功能上線前的最后一環(huán),對于一個應(yīng)用的意義是尤為重要的。如果僅僅是去完成了一個項目而不知道這個項目的某個具體需求的使用率和用反饋率,這樣顯然不是我們想看到的。尤其是在互聯(lián)網(wǎng)行業(yè)里,每年也許都有不同的風口,每個月也許都有不同的業(yè)務(wù)上線,如果沒有統(tǒng)計埋點的數(shù)據(jù)化反饋,是無法理性的感知一個功能的完成是否能吸引用戶或者解決用戶的一個痛點。也只有量變才能慢慢的達到質(zhì)變。

目前主流的統(tǒng)計平臺多種多樣,國外有谷歌分析(GA),國內(nèi)有GrowingIO、友盟統(tǒng)計、騰訊移動統(tǒng)計等等,而每個統(tǒng)計都有自己的優(yōu)缺點,但是萬變不離其宗,根本統(tǒng)計方案都是大同小異的。對創(chuàng)業(yè)公司而言,每個公司的產(chǎn)品流動性不確定,產(chǎn)品崗的同學對于不同的統(tǒng)計平臺了解程度又不同,所以經(jīng)常會出現(xiàn)換統(tǒng)計平臺的情況。項目剛啟動的時候還好,但是如果項目已經(jīng)很穩(wěn)定而且相對龐大之后,這個工作量是恐怖的,你不會是想做,而是想死。最終的情況可能會是,技術(shù)不想動,產(chǎn)品要求換,這樣就出現(xiàn)了多統(tǒng)計平臺的尷尬界面,并不是根本的解決問題,而是應(yīng)付彼此,坑害后面接手項目的人。如果你的項目已經(jīng)是有了上面的情況,那么請你認真考慮和權(quán)衡一下是否需要重新審視一下自己項目的統(tǒng)計模塊---這個項目的即是項目的結(jié)束,又是項目的開始的模塊。

方案與選擇

下面我們就來逐步分析一下目前iOS主流的埋點方案和各個方案的優(yōu)缺點,每種方案都沒有絕對的對錯,這些方案都能完成產(chǎn)品的需求,但是如果我們在各自的團隊內(nèi),有時間和有機會把埋點做的更好,那也是一件很酷的事情。我們可以根據(jù)自己目前的情況來選擇最適合項目內(nèi)使用的方案。

目前我找到的公開的最全面的埋點方案網(wǎng)易HubbleData無痕埋點SDK實現(xiàn)

把埋點劃分為3大類
1.代碼埋點
2.可視化埋點
3.無埋點

我們先來重新理解一下文中所提及的這三類埋點方式,因為第一次我在看的時候,也沒有對后兩種方式有特別清晰的理解。

1.代碼埋點

對于第一類代碼埋點,更準確的理解是純手工埋點,因為不可能埋點不需要代碼,即使是最后一種所謂的無埋點,也不可能做到不寫代碼就達到統(tǒng)計的效果。我們用GA舉個例子,其他統(tǒng)計的原理一樣,只不過是統(tǒng)計的代碼有些不一樣罷了。

對于頁面統(tǒng)計:

//這是封裝的統(tǒng)計管理類里的一個統(tǒng)計頁面的方法
+(void)userPushToGAScreenName:(NSString *)GAScreenName
{
id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker];
[tracker set:kGAIScreenName value:GAScreenName];
[tracker send:[[GAIDictionaryBuilder createScreenView] build]];
}

因為早期我們在項目架構(gòu)的時候,初始化了一個BaseController,在BaseController的viewWillAppear和viewWillDisappear里嵌入工具類的這個方法,就可以對GA發(fā)送一條統(tǒng)計消息。每個項目類名對應(yīng)一個頁面,通過映射關(guān)系來管理頁面的統(tǒng)計,這個地方用了一個宏文件來保存記錄的屏幕名稱ScreenName,可能要使用大量的if()else()來做判斷,這種方案的后續(xù)維護是反人類的,相對較好的方式如果不用這種大量判斷的話,可以本地維護一個plist文件,不過后續(xù)的維護依舊繁瑣,新來的小伙伴的操作成本是很高的。

針對事件統(tǒng)計:

+(void)GACreateEventWithCategory:(NSString *)category action:(NSString *)action label:(NSString *)label value:(NSNumber *)value withScreenName:(NSString *)screenName
{
id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker];
[tracker set:kGAIScreenName value:screenName];
[tracker send:[[GAIDictionaryBuilder createEventWithCategory:category action:action label:label value:value] build]];
[tracker set:kGAIScreenName value:nil];
}

對于事件統(tǒng)計,就需要我們深入到每個業(yè)務(wù)的內(nèi)部去調(diào)用一下這個方法,來把事件的頁面,方法名等參數(shù)傳遞進來,有時候還有一些用戶的自定義操作,需要攜帶一些特殊的參數(shù)value進來。這個后期的維護工作簡直是恐怖的,可能你寫一個版本的統(tǒng)計之后,你也并不能驗證這個統(tǒng)計是否就完全正確,交給產(chǎn)品去驗收的話他們也不一定能測試出來是否起了效果。(特別是谷歌統(tǒng)計的原因,所有的統(tǒng)計都有滯后性,并不能立刻反饋,驗收難度和時間成本都很高)。

缺點:

1.顯而易見,你會在后期維護的時候?qū)懙膽岩扇松?br> 2.復用性差,幾乎不能移植給其他項目
3.工作量大,而且會越寫越多
4.統(tǒng)計代碼上線之后,如果出現(xiàn)問題,只能后續(xù)版本迭代
5.如果統(tǒng)計項目名字改變了,原來老的APP版本依舊會統(tǒng)計老的頁面名字

優(yōu)點:

1.如果非要寫一個其他統(tǒng)計無法做到的優(yōu)點的話,那就是可自定義程度高吧,統(tǒng)計代碼想寫到那里寫到那里(其實這些也可以在后面的方案實現(xiàn),只是實現(xiàn)上稍微麻煩一點罷了)
2.最容易想到的方案(前期費時少,使用起來費手不費思路)

如果你受夠了寫上面的代碼(不管你們受夠了沒有,反正我是受的夠夠的了),那么我們來分析下面的2種方案。

2.可視化埋點

“可視化埋點即用可視化交互的方式圈選出所要采集數(shù)據(jù)的控件,當用戶行為產(chǎn)生時,即可收集到相應(yīng)的埋點數(shù)據(jù)。相比于前面的代碼埋點而言,可視化埋點能夠解決代碼埋點代價大成本高的問題,但是無法靈活的自定義埋點屬性。”
可視化埋點上文中的定義很繞,我最后的理解是:動態(tài)下發(fā)后臺配置的埋點數(shù)據(jù),利用AOP的方式method swizzling需要統(tǒng)計的事件,添加統(tǒng)計代碼進行統(tǒng)計,最后上傳給后臺統(tǒng)計結(jié)果。
這里我們要提一下AOP(切面編程思想),OC這邊有一個很出名的開源庫Aspects,利用Aspects,我們可以很方便的method swizzling攔截我們想要處理的方法,在執(zhí)行對應(yīng)方法前后插入我們的統(tǒng)計代碼。具體的原理請參考微信讀書團隊Aspects的基本原理或者我寫的這篇iOS 面向切面編程(AOP)開源框架Aspects源碼解讀

該方案的具體步驟就是:
(1)從后臺獲取需要統(tǒng)計的地方
(2)hook住需要統(tǒng)計的類的load方法來Method Swizzing要統(tǒng)計的方法
(3)上傳統(tǒng)計到的事件給后臺分析

我們用UIViewController,UITablview(collectionView與tableView基本相同),UIControl為例子,講解一下該方案的思路。

1.UIViewController PV統(tǒng)計,頁面的統(tǒng)計較為簡單,利用Method Swizzing hook 系統(tǒng)的viewDidLoad, 直接通過頁面名稱即可鎖定頁面的展示代碼如下:

+(void)load { 
     static dispatch_once_t onceToken; 
     dispatch_once(&onceToken, ^{ 
     SEL originalDidLoadSelector = @selector(viewDidLoad); 
     SEL swizzingDidLoadSelector = @selector(analytic_viewDidLoad); 
     [MethodSwizzingTool swizzingForClass:[self class] originalSel:originalDidLoadSelector swizzingSel:swizzingDidLoadSelector]; 
}); 
}
 -(void)analytic_viewDidLoad {
    [self  analytic_viewDidLoad]; 
    //用當前類的類名作為統(tǒng)計頁面的標識符  
    NSString * identifier = [NSString stringWithFormat:@"%@", [self class]];
     //通過當前類名獲取PAGEPV表內(nèi)的對應(yīng)的頁面的pageid和pagename  
     NSDictionary * dic = [[[AnalyticTool shareInstance].data objectForKey:@"PAGE"] objectForKey:identifier]; 
     if (dic) { 
     NSString * pageid = dic[@"screenData"][@"pageid"]; 
     NSString * pagename = dic[@"screenData"][@"pagename"]; 
     [AnalyticTool upLoadScreenName:pagename withScreenID:pageid]; 
     }
}

2.UIControl 點擊統(tǒng)計,主要通過hook sendAction:to:forEvent: 來實現(xiàn), 其唯一標識符我們用 targetname/selector/tag來標記,具體代碼如下:

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

-(void)analytic_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event 
{ 
     [self analytic_sendAction:action to:target forEvent:event];
     NSString * identifier = [NSString stringWithFormat:@"%@/%@/%ld", [target class], 
     NSStringFromSelector(action),self.tag]; NSDictionary * dic = [[[AnalyticTool shareInstance].data objectForKey:@"ACTION"] objectForKey:identifier]; 
     if (dic) {
     NSString * eventid = dic[@"ActionData"][@"eventid"]; 
     NSString * targetname = dic[@"ActionData"][@"target"]; 
     NSString * pageid = dic[@"ActionData"][@"pageid"]; 
     NSString * pagename = dic[@"ActionData"][@"pagename"];
     [AnalyticTool upLoadActionEventWithScreenName:pagename withScreenID:pageid withTargetName:targetname withEventID:eventid]; 
    }
}

3.TableView (CollectionView) 的代理統(tǒng)計
tablview的唯一標識, 我們使用 delegate.class/tableview.class/tableview.tag的組合來唯一鎖定。 主要是通過hook setDelegate 方法, 在設(shè)置代理的時候再去hook住對應(yīng)的代理方法來實現(xiàn)。因為tableview有多種代理方法,這里就不展開講解了,具體方法跟上面的是類似的。就是多hook了一步。

缺點:

1.需要后臺配合
2.可拓展性不是很高,因為需要修改后臺下發(fā)的統(tǒng)計內(nèi)容來每次的版本統(tǒng)計擴展

優(yōu)點:

1.相對于第一種方案,代碼量少了很多。
2.動態(tài)化從后臺獲取統(tǒng)計內(nèi)容,方便線上修改

3.無埋點

其實無埋點才是真正的全埋點,本質(zhì)上是利用runtime的方法也是利用AOP的思想,把所有的頁面,事件,都給hook住然后自己定義一個層級概念,需要前段和后端協(xié)助開發(fā)一套完整的統(tǒng)計,把用戶在應(yīng)用內(nèi)的全部操作都上傳給服務(wù)器,然后服務(wù)器篩選出來需要的內(nèi)容進行展示。這里需要再次提及一下上面的文章網(wǎng)易HubbleData無痕埋點SDK實現(xiàn)
這是詳細講解了無痕埋點的具體原理和難點,重點在于作者對普通view的層級結(jié)構(gòu)path構(gòu)造,非常巧妙,可以借鑒一下。

可視化埋點和無埋點最大的不同在于,前者是服務(wù)器定義統(tǒng)計的內(nèi)容,后者是把全部內(nèi)容統(tǒng)計然后提供給后臺,后臺可以自己選擇需要的內(nèi)容來使用。后者的可拓展性和動態(tài)性更高。

缺點:

1.對于小團隊來說,幾乎無法做到,因為需要權(quán)衡這個架構(gòu)要求的時間
2.需要后臺配合,前端業(yè)務(wù)量少了,但是后臺的業(yè)務(wù)量沒有減少,需要共同維護

優(yōu)點:

1.前段代碼可移植程度高
2.完全無侵入統(tǒng)計,業(yè)務(wù)代碼不受影響。
3.前端統(tǒng)計非常安逸,幾乎不需要做什么事情。
4.因為這種方式是全統(tǒng)計,所以不存在動態(tài)化了根本就,后面業(yè)務(wù)只要摘取需要的內(nèi)容就可以,數(shù)據(jù)量充足。

寫在最后

最好的方案永遠是針對于不同的場景來說的,我們不可能在一個創(chuàng)業(yè)團隊一開始就選擇方案3的架構(gòu),所以對于你來說,你要自己抉擇目前而言對你最好的方案,如果你沒有后臺業(yè)務(wù)同學的支持,方案1也許對你來說真的是最好的方案了,起碼是可以完成統(tǒng)計需求,雖然苦點累點。但是在合適的時間,切換不同的選擇,才是成長的體現(xiàn),還是最開始的話,如果你在的團隊,已經(jīng)給你了資源和時間去完善埋點這個模塊,如果你把它做的更好,那一定是一件很酷的事情。

參考資料

1.網(wǎng)易HubbleData無痕埋點SDK實現(xiàn)
2.iOS無埋點數(shù)據(jù)SDK實踐之路
3.可視化埋點方案
4.美團前端無痕埋點方案
5.iOS打點雜談
6.iOS動態(tài)性可復用而且高度解耦的用戶統(tǒng)計埋點實現(xiàn)
7.微信讀書團隊Aspects的基本原理
8.iOS 面向切面編程(AOP)開源框架Aspects源碼解讀

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

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,065評論 25 708
  • 0 引言 最近在負責公司的HubbleData的埋點SDK的開發(fā)任務(wù),產(chǎn)品的雛形其實在幾年前就已經(jīng)有了,公司內(nèi)部的...
    魯冰閱讀 10,103評論 9 61
  • 前言 最近跟同事花了點時間來思考可視化埋點,并沒有什么突破性的進展,不過市面上很多關(guān)于可視化埋點的技術(shù)文章都在講達...
    daixunry閱讀 8,073評論 1 38
  • 昨晚看完球賽就睡著了,看的時候不是很困,但是累吧。。 早上來的時候校廣播在放 愛如潮水 所以現(xiàn)在我也在聽 太累了,...
    宋長金j閱讀 176評論 0 0
  • 文|凡縷 聽說,在廣州那個地方,洗澡叫沖涼。天熱,沖個澡涼快,重點是個“沖”字。 但是在俺農(nóng)村老家,洗澡叫抹汗。從...
    凡縷閱讀 351評論 8 10