問(wèn)題記錄:iOS用戶(hù)行為統(tǒng)計(jì)代碼的剝離
這兩天在搞一個(gè)統(tǒng)計(jì)模塊,把碰到的問(wèn)題和一些討論記錄下來(lái),所以本文沒(méi)有答案,沒(méi)有解決方案,僅是討論而已。
我要做什么?
我現(xiàn)在做的是一個(gè) app 里面的用戶(hù)行為統(tǒng)計(jì),簡(jiǎn)單來(lái)說(shuō)就是記錄下用戶(hù)從哪個(gè)頁(yè)面跳轉(zhuǎn)到哪個(gè)頁(yè)面,在頁(yè)面上都點(diǎn)擊了哪些按鈕,點(diǎn)擊了幾次等等之類(lèi)的東西。
統(tǒng)計(jì)工具用的是現(xiàn)成的 Google Analytics,F(xiàn)lurry,MixPanel,我要做的就是將他們集成進(jìn)我的統(tǒng)計(jì)模塊代碼,并進(jìn)行各種業(yè)務(wù)事件的統(tǒng)計(jì)。
以Flurry為例,當(dāng)點(diǎn)擊登錄按鈕clickShootButtonAction被調(diào)用要做一條用戶(hù)行為統(tǒng)計(jì),統(tǒng)計(jì)代碼是這樣的
- -?(IBAction)clickShootButtonAction
- {
- ????//?執(zhí)行下面這句話(huà),在?Flurry?的后臺(tái)就能看到這個(gè)事件的記錄
- ????[Flurry?logEvent:@"點(diǎn)擊拍照按鈕"];
- }
或者
- -?(IBAction)clickShootButtonAction
- {
- ????//?執(zhí)行下面這句話(huà),在?Flurry?的后臺(tái)就能看到這個(gè)事件的記錄
- ????//?與上面不同的是,這邊記錄的是用戶(hù)的一條行為路徑
- ????//?表示:用戶(hù)拍照后,在照片分享頁(yè),將照片分享到了?Facebook
- ????[Flurry?logEvent:@"保存分享照片"?withParameters:{@"分享到":@"Facebook"}];
- }
而在應(yīng)用里面記錄上百個(gè)用戶(hù)行為是很正常的事情。也就說(shuō)類(lèi)似上面的這種代碼在 Controller 里面可能要出現(xiàn)幾百次,還散落在各處。
這是正常的嗎?
如果這些代碼遍布我們的工程,使得統(tǒng)計(jì)模塊和業(yè)務(wù)代碼耦合度極高,造成剝離困難,無(wú)法重用等等各種的問(wèn)題,寫(xiě)起來(lái)手累心也累。
對(duì)我們來(lái)說(shuō),最理想的情況下,Controller 里面這種代碼越少越好,***是一行都沒(méi)有,包含個(gè)頭文件就能自動(dòng)統(tǒng)計(jì)那該多好。因?yàn)槿绻獎(jiǎng)冸x統(tǒng)計(jì)代碼,或者更換統(tǒng)計(jì)方式,都是非常方便。
但實(shí)際情況不容樂(lè)觀?。?!
有解嗎?
根據(jù)統(tǒng)計(jì)的事件,我們把需要統(tǒng)計(jì)的方法大致歸類(lèi)為以下三種,統(tǒng)計(jì)剝離的難度也逐級(jí)遞增
- 類(lèi)似 viewDidLoad、viewWillAppear 這種 ViewController 的生命周期的方法;
- 調(diào)用就進(jìn)行一次事件統(tǒng)計(jì)的方法;
- 在方法內(nèi),滿(mǎn)足條件才統(tǒng)計(jì)的方法;
那么問(wèn)題來(lái)了,如果我們不希望在 Controller 里面直接添加統(tǒng)計(jì)代碼,應(yīng)該怎么統(tǒng)計(jì)上面的三種方法?
剝離統(tǒng)計(jì) ViewController 生命周期的統(tǒng)計(jì)代碼
這類(lèi)方法的統(tǒng)計(jì)比較簡(jiǎn)單,寫(xiě)個(gè) UIViewController 的 category,hook UIViewController 的中需要統(tǒng)計(jì)的方法,然后將頭文件塞到要統(tǒng)計(jì)的 ViewController 即可。
剝離調(diào)用一次就統(tǒng)計(jì)的統(tǒng)計(jì)代碼
這個(gè)統(tǒng)計(jì)代碼的剝離比較麻煩,麻煩的點(diǎn)在于這些方法是根據(jù)業(yè)務(wù)邏輯產(chǎn)生的,每個(gè) ViewController 中的方法都不一致,沒(méi)法用統(tǒng)一的方式來(lái)處理。
關(guān)于這類(lèi)代碼的剝離,我查了一些資料,請(qǐng)教一些同學(xué),又在一些技術(shù)群討論了下,確實(shí)可以剝離,但方法都不太可靠,所以不建議使用,以下一一列舉。
- 方法一:AOP + SPOC 配置文件?
這個(gè)方法來(lái)自最近上傳的《禪與 Objective-C 編程藝術(shù)》***一小節(jié) 面向切面編程 中。通過(guò)在 SPOC 配置文件中添加類(lèi)名和方法,hook 類(lèi)里面的方法,然后進(jìn)行統(tǒng)計(jì)。
乍看之下,這個(gè)思路非常不錯(cuò),寫(xiě)起來(lái)爽歪歪,要添加統(tǒng)計(jì)代碼時(shí),只要在配置文件中加一下就 OK,嗨到不行。但后來(lái)一想這種方式實(shí)際執(zhí)行起來(lái)是會(huì)有問(wèn)題的。
首先統(tǒng)計(jì)模塊代碼和業(yè)務(wù)代碼是分散在兩個(gè)地方,統(tǒng)計(jì)是根據(jù)具體的類(lèi)名和方法名的字符串來(lái) hook 后進(jìn)行統(tǒng)計(jì)操作。因?yàn)榻y(tǒng)計(jì)模塊比較獨(dú)立,由一個(gè)單獨(dú)的人來(lái)寫(xiě),其他人去寫(xiě)業(yè)務(wù)代碼。業(yè)務(wù)開(kāi)發(fā)的同學(xué)隨意修改個(gè)方法名還是比較正常的。當(dāng)業(yè)務(wù)開(kāi)發(fā)的同學(xué)改了個(gè)方法名,一般不會(huì)想到統(tǒng)計(jì)這邊也要改;統(tǒng)計(jì)這邊的同學(xué)也不知道業(yè)務(wù)的同學(xué)改了什么。所以這種只有調(diào)試時(shí),碰到統(tǒng)計(jì)異常才會(huì)去檢查這里,可維護(hù)性太差。
所以這種方法是寫(xiě)的人爽,維護(hù)的人非常非常的不爽!?
所以這種不推薦使用!
- 方法二:約定方法名?
從開(kāi)發(fā)的開(kāi)始就約定好,比如需要統(tǒng)計(jì)的函數(shù)(例如 IBAction 之類(lèi)的,按照約定命名),然后 hook objc-sendmessage 函數(shù),統(tǒng)計(jì)所需的方法。這個(gè)方法其實(shí)和方法一類(lèi)似,所以也有同樣的問(wèn)題。而且人為約定沒(méi)有特別強(qiáng)的約束力,稍不留神就忘了方法名的約定,而且也沒(méi)有編譯器警告、錯(cuò)誤的提示,根本就不會(huì)想到這時(shí)候要按照約定的規(guī)則命名。再且需求是經(jīng)常變動(dòng)的,有些原先不需要統(tǒng)計(jì)的內(nèi)容,后來(lái)突然又要統(tǒng)計(jì)了,按照這種規(guī)則就要改方法名,整體的感覺(jué)就是場(chǎng)面太混亂了。
所以這種也不推薦使用!
剝離滿(mǎn)足條件才統(tǒng)計(jì)的統(tǒng)計(jì)代碼
無(wú)解!
想過(guò)去真真是無(wú)解?。∫?yàn)槲覀兦懊嫠玫姆椒ê退悸坊倦x不開(kāi) AOP,而 AOP 本身是 hook 一整個(gè)方法,在方法前后添加一些自定義的操作。AOP 是沒(méi)有辦法了解方法內(nèi)部是什么樣的,更何談去統(tǒng)計(jì)方法內(nèi)滿(mǎn)足了一定條件再統(tǒng)計(jì)事件。
怎么辦?
老老實(shí)實(shí)的將統(tǒng)計(jì)代碼寫(xiě)到相關(guān)的方法里面吧,真沒(méi)轍了!
***的憂(yōu)傷
折騰了這么些來(lái)回,結(jié)果還是沒(méi)法將統(tǒng)計(jì)代碼和業(yè)務(wù)代碼分開(kāi)。原以為統(tǒng)計(jì)模塊應(yīng)該是一個(gè)獨(dú)立的模塊,結(jié)果卻捅到各個(gè)Controller代碼里面去,實(shí)在令人憂(yōu)傷。
那么到底是什么原因造成了這樣一個(gè)結(jié)局?
回頭看看,我們一開(kāi)始就默認(rèn)了統(tǒng)計(jì)模塊是一個(gè)獨(dú)立的模塊,應(yīng)該與業(yè)務(wù)邏輯分開(kāi)來(lái)。但實(shí)際上統(tǒng)計(jì)是和業(yè)務(wù)緊密結(jié)合的一個(gè)模塊,所以是不是可以這么想。統(tǒng)計(jì)模塊的代碼也屬于業(yè)務(wù)邏輯呢?
不管怎樣,這么想就可以心安理得的把統(tǒng)計(jì)代碼寫(xiě)進(jìn) Controller 了~