統(tǒng)計這個事情可以說是個巨無語的系統(tǒng)捏鱼,當(dāng)然不把他獨立出來也就不是什么問題了,只是一堆牛皮癬似得代碼穿插在項目各個地方聂渊,畢竟真正應(yīng)用到一個app里的統(tǒng)計都跟業(yè)務(wù)有著很強的綁定關(guān)系差购,脫離業(yè)務(wù)的統(tǒng)計數(shù)據(jù)基本沒什么大用,先吐槽一波再開始正文汉嗽。。找蜜。饼暑。
基礎(chǔ)封裝
先從用第三方的來說,基本上就只是需要包個殼就ok了洗做,建個manager弓叛,初始化sdk一封裝,加幾個常用統(tǒng)計方法基本ok诚纸。常見的方法就是傳個event名再加個properties傳擴展字典撰筷,用戶登錄狀態(tài)綁定注銷,通用字段增刪改畦徘,基本上這就滿足了大部分需求毕籽。抬闯。。
如果是純自己手寫关筒,上面說的殼放著溶握,剩下的仿照sdk來,基本功能要實現(xiàn)異步隊列記錄往本地寫數(shù)據(jù)蒸播,定期上傳睡榆,處理好讀寫關(guān)系是關(guān)鍵,這里不多說不是這篇重點袍榆。
ps:有個殼才敢放開手折騰優(yōu)化
切面封裝
切面統(tǒng)計其實可以看我之前的IOS 百行代碼切面日志胀屿,整個完成的就是切面的封裝,看過的基本應(yīng)該了解這套邏輯切面的時候不關(guān)心切面方法的參數(shù)的話會非常好用包雀。
如果業(yè)務(wù)關(guān)聯(lián)強的情況碉纳,雖然也能處理但要針對那些業(yè)務(wù)作出對應(yīng)的邏輯,導(dǎo)致切面封裝里夾雜很多特殊邏輯,下面的方法內(nèi)部要對originAOP拆分取所有參馏艾,甚至要復(fù)制部分業(yè)務(wù)層邏輯過來最后完成一個統(tǒng)計劳曹。
-(void)al_logger:(id)log originAOP:(id)originAOP
所以我個人建議這里就封裝些簡單的少參甚至無參的統(tǒng)計,然后基本上簡單的通用統(tǒng)計和業(yè)務(wù)統(tǒng)計建個類或者plist琅摩,列一下要切面的類和方法就完成了铁孵。
凡事都有特例,如果本身方法內(nèi)多個參數(shù)完成一次統(tǒng)計就不多房资,那我之前的百行系列就已經(jīng)足夠你解耦了蜕劝。
ps:鄭重聲明AOP用Aspects不支持切面靜態(tài)方法,回頭有空肯定是要改的轰异,Aspects用class_replaceMethod實現(xiàn)的時候類方法沒有取isa的過程岖沛,類方法找不到,其實是可以實現(xiàn)的搭独,回頭我重寫一個關(guān)于swizzing的庫就換掉Aspects婴削。
通用統(tǒng)計
通用統(tǒng)計就是可以脫離業(yè)務(wù)完全抽離的部分,這部分其實可以設(shè)計部分業(yè)務(wù)承接牙肝,比如通用點擊事件可以把點擊的UI對象擴展出一個字典的屬性值唉俗,如此對于通用統(tǒng)計來說只是看看有沒有某個屬性有的話就扔到統(tǒng)計里,和業(yè)務(wù)層沒有關(guān)聯(lián)配椭,但后面帶來的好處很大虫溜!這個最后說。
App生命周期統(tǒng)計
應(yīng)用生命周期的統(tǒng)計隨便建個類股缸,監(jiān)聽下面的通知就ok了衡楞,當(dāng)然這個類就不能銷毀了,比較懶得做法直接AOPLogger 類擴展一下init里監(jiān)聽敦姻,因為我本身的類里連init方法都沒重寫瘾境。
UIApplicationDidFinishLaunchingNotification (通知名稱) ---> application:didFinishLaunchingWithOptions:(委托方法):在應(yīng)用程序啟動后直接進(jìn)行應(yīng)用程序級編碼的主要方式歧杏。
UIApplicationWillResignActiveNotification(通知名稱)--->applicationWillResignActive:(委托方法):用戶按下主屏幕按鈕調(diào)用 ,不要在此方法中假設(shè)將進(jìn)入后臺狀態(tài)寄雀,只是一種臨時變化得滤,最終將恢復(fù)到活動狀態(tài)
UIApplicationDidBecomActiveNotification(通知名稱) ---->applicationDidBecomeActive:(委托方法):應(yīng)用程序按下主屏幕按鈕后想要將應(yīng)用程序切換到前臺時調(diào)用,應(yīng)用程序啟動時也會調(diào)用盒犹,可以在其中添加一些應(yīng)用程序初始化代碼
UIApplicationDidEnterBackgroundNotification(通知名稱)----->applicationDidEnterBackground:(委托方法):應(yīng)用程序在此方法中釋放所有可在以后重新創(chuàng)建的資源懂更,保存所有用戶數(shù)據(jù),關(guān)閉網(wǎng)絡(luò)連接等急膀。如果需要沮协,也可以在這里請求在后臺運行更長時間。如果在這里花費了太長時間(超過5秒)卓嫂,系統(tǒng)將斷定應(yīng)用程序的行為異常并終止他慷暂。
UIApplicationWillEnterForegroundNotification(通知名稱) ---->applicationWillEnterForeground:(委托方法):當(dāng)應(yīng)用程序在applicationDidEnterBackground:花費了太長時間,終止后晨雳,應(yīng)該實現(xiàn)此方法來重新創(chuàng)建在applicationDidEnterBackground中銷毀的內(nèi)容行瑞,比如重新加載用戶數(shù)據(jù)、重新建立網(wǎng)絡(luò)連接等餐禁。
UIApplicationWllTerminateNotification(通知名稱) ----> applicationWillTerminate:(委托方法):現(xiàn)在很少使用血久,只有在應(yīng)用程序已進(jìn)入后臺,并且系統(tǒng)出于某種原因決定跳過暫停狀態(tài)并終止應(yīng)用程序時帮非,才會真正調(diào)用它氧吐。
點擊事件(不包括手勢)
這里點擊事件切面分為3類
1.UIControl的addTarget觸發(fā),UIButton都會走UIApplication里的這方法末盔,但需要忽略一部分筑舅,不然會和第二個部分重疊,而且手勢addTarget不走陨舱,不過手勢不走正好可以區(qū)分手勢
-(void)sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event;
2.設(shè)置delegate委托式系統(tǒng)點擊觸發(fā)翠拣,這里直接切了UICollectionView,UITableView隅忿,UITabBarController心剥,UITabBar,UIAlertView背桐,UIActionSheet這幾個setDelegate方法,然后加標(biāo)識判斷切委托方法蝉揍,用Aspects庫的方法swizzing因為他不允許重復(fù)swizzing算雙保險链峭。
3.UIAlertAction切block屬性的set方法,每次setBlock的時候替換成我自己的block又沾,在我的block內(nèi)執(zhí)行設(shè)置的block弊仪。
下面就是協(xié)議熙卡,用pod的話AOPLogger/AOPClick就有了,用的時候類擴展一下AOPLogger遵守協(xié)議實現(xiàn)對應(yīng)方法
@protocol AOPLoggerClickProtocol <NSObject>
@optional
- (void)alcp_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event;
- (void)alcp_customIgnore_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event;
- (void)alcp_collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath from:(id)sender;
- (void)alcp_tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath from:(id)sender;
- (void)alcp_tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController from:(id)sender;
- (void)alcp_tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item from:(id)sender;
- (void)alcp_alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex from:(id)sender;
- (void)alcp_actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex from:(id)sender;
- (void)alcp_alertControllerAction:(UIAlertAction *)action from:(id)sender;
@end
ps:覆蓋可能不全励饵,漏了什么歡迎git提pull request
手勢統(tǒng)計
這個是切了UIView的addGestureRecognizer方法驳癌,每次添加手勢的時候我多加個自己AOPLogger的taget-action到gestureRecognizer對象上,這樣手勢統(tǒng)計就搞定了役听。
如果要用這個統(tǒng)計點擊和長按颓鲜,判斷一下狀態(tài)是結(jié)束的時候統(tǒng)計gestureRecognizer類型是UITapGestureRecognizer,UILongPressGestureRecognizer就行,其實手勢的統(tǒng)計或許都在結(jié)束的時候統(tǒng)計區(qū)分下類型就夠了吧典予!
@protocol AOPLoggerGestureRecognizerProtocol <NSObject>
@optional
-(void)algrp_handleGesture:(UIGestureRecognizer*)gestureRecognizer;
@end
頁面統(tǒng)計
這個太尋常了甜滨,感覺沒什么好說,使用方法如上瘤袖,之所以還提供個pod的AOPLogger/AOPPageView,主要是如果要統(tǒng)計瀏覽時長邏輯衣摩,自己方便實現(xiàn)(如顯示的時候runtime隨便塞個date進(jìn)去,消失的時候算一下上報)捂敌,還有我默認(rèn)預(yù)先忽略了部分頁面,省了自己寫艾扮。
@protocol AOPLoggerPageViewProtocol <NSObject>
@optional
-(BOOL)alpvp_viewIgnore:(id)sender;
-(void)alpvp_viewDidAppear:(BOOL)animated sender:(id)sender;
-(void)alpvp_viewDidDisappear:(BOOL)animated sender:(id)sender;
@end
業(yè)務(wù)統(tǒng)計
業(yè)務(wù)統(tǒng)計首先放的位置要保證在對應(yīng)業(yè)務(wù)線的目錄里,畢竟跟業(yè)務(wù)關(guān)聯(lián)密切占婉,大的業(yè)務(wù)邏輯調(diào)整的時候看一下放業(yè)務(wù)統(tǒng)計目錄下對應(yīng)的地方需不需要修改泡嘴。
無參數(shù)方法統(tǒng)計
這一類就是需要寫在業(yè)務(wù)層里但統(tǒng)計的時候只記錄一個Event名,比如有個方法-(void)abc:(id)a b:(id)b c:(id)c;我只是記錄他被調(diào)用锐涯,這種直接AOPLogger調(diào)用下[AOPLogger AOPLoggerWithClassString:classString methodString:@"abc:b:c" log:@"abc"]或扔plist里默認(rèn)直接解決磕诊。
方法內(nèi)邏輯處理統(tǒng)計
這個就有點厲害了,比如下訂單的時候我買了一堆東西里面帶著各種商品屬性纹腌,要你把某種屬性的商品做統(tǒng)計霎终,只是舉例...這時候直接寫這個類的類擴展load方法里切面然后寫邏輯處理統(tǒng)計,這里不建議用Aspects庫升薯,底層保證切一個類的一個方法一次是好用的莱褒,可業(yè)務(wù)層各種奇葩邏輯都會有,想解耦經(jīng)常用奇招所以最好允許多次切面涎劈,dispatch_once保證自己的邏輯需要的切一次就可以了广凸,可以用我簡單封裝的方法。
@interface NSObject (AOPLogger)
/**
替換或添加類方法蛛枚,即使替換過也會替換谅海,注意想單次替換使用dispatch_once保證,如果方法從未聲明過則會添加失敗
@param originalSelector 原方法
@param swizzledSelector 替換方法
@param error 錯誤信息
*/
+(void)al_hookOrAddWithOriginClassSeletor:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector error:(NSError**)error;
/**
替換或添加實例方法蹦浦,即使替換過也會替換扭吁,注意想單次替換使用dispatch_once保證,如果方法從未聲明過則會添加失敗
@param originalSelector 原方法
@param swizzledSelector 替換方法
@param error 錯誤信息
*/
+(void)al_hookOrAddWithOriginSeletor:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector error:(NSError**)error;
@end
前后邏輯關(guān)聯(lián)統(tǒng)計
這個可以結(jié)合點擊來講,比上面還要讓人頭疼侥袜,類似統(tǒng)計我查看一個東西蝌诡,要記錄我在哪點擊的查看以及當(dāng)前頁面的數(shù)據(jù)屬性,還有從哪來到當(dāng)前頁面的枫吧,要查看東西的id浦旱。這個時候就需要結(jié)合通用統(tǒng)計來搞了,我們在最底層可以做的是把本身點擊的UI對象的默認(rèn)字典屬性和最上層頁面的默認(rèn)字典屬性以及點擊對象target上層UI對象的默認(rèn)字典屬性做統(tǒng)計九杂,最上層的激活頁面這個是可以在任何地方搞出來的颁湖,回頭我可以把類擴展放上來,在任何頁面都可以拿到當(dāng)前頁面尼酿,前一個頁面以及頁面屬于第幾層爷狈。底層在響應(yīng)默認(rèn)點擊事件之前處理我們的統(tǒng)計邏輯,接下來就是怎么把屬性賦值涎永,這個時候就需要我們?nèi)デ忻娈?dāng)前頁面里做model賦值的方法然后同時把想要統(tǒng)計的屬性賦值到當(dāng)前頁面的默認(rèn)字典屬性里鹿响,如果這是cell上的button那么賦值cell的時候就可以切面惶我。
總的來說就是尋找UI對象賦值點,然后切面為UI對象添加統(tǒng)計屬性盯蝴,我們runtime加到所有UI對象上隱藏的默認(rèn)字典屬性字段專門承接關(guān)聯(lián)信息捧挺,底層只做讀取整合闽烙,業(yè)務(wù)層切面賦值声搁。
這個時候就會發(fā)現(xiàn)數(shù)據(jù)對象與UI對象綁定協(xié)議的制定會直接決定實現(xiàn)的復(fù)雜度疏旨。
后序
業(yè)務(wù)層的統(tǒng)計我個人認(rèn)為原則上盡量后端做這個事檐涝,畢竟要保證統(tǒng)計數(shù)據(jù)的正確性霞玄,其實減少前端統(tǒng)計很重要拉岁,畢竟現(xiàn)在web(pc網(wǎng)頁)惰爬,wap(手機網(wǎng)頁)撕瞧,小程序,安卓丛版,ios一牽扯業(yè)務(wù)邏輯統(tǒng)計非常需要統(tǒng)一校準(zhǔn)页畦,不然數(shù)據(jù)絕對超乎你的想象。
歸納統(tǒng)計解耦思路
統(tǒng)計放到其他端其實解耦思路也是大致相同独令。燃箭。舍败。下面總結(jié):
1.找底層觸發(fā)的事件點
2.業(yè)務(wù)層找賦值點
3.使用AOP的思路對上面的地方進(jìn)行插入我們的統(tǒng)計邏輯