統(tǒng)計這個事情可以說是個巨無語的系統(tǒng),當(dāng)然不把他獨立出來也就不是什么問題了忍宋,只是一堆牛皮癬似得代碼穿插在項目各個地方,畢竟真正應(yīng)用到一個app里的統(tǒng)計都跟業(yè)務(wù)有著很強(qiáng)的綁定關(guān)系震叮,脫離業(yè)務(wù)的統(tǒng)計數(shù)據(jù)基本沒什么大用诗眨,先吐槽一波再開始正文。凄硼。铅协。。
基礎(chǔ)封裝
先從用第三方的來說摊沉,基本上就只是需要包個殼就ok了狐史,建個manager,初始化sdk一封裝,加幾個常用統(tǒng)計方法基本ok骏全。常見的方法就是傳個event名再加個properties傳擴(kuò)展字典苍柏,用戶登錄狀態(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)強(qiáng)的情況,雖然也能處理但要針對那些業(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 至少我現(xiàn)在還沒發(fā)現(xiàn)IOS里怎么實現(xiàn)切面靜態(tài)方法(類方法)
通用統(tǒng)計
通用統(tǒng)計就是可以脫離業(yè)務(wù)完全抽離的部分自点,這部分其實可以設(shè)計部分業(yè)務(wù)承接桐罕,比如通用點擊事件可以把點擊的UI對象擴(kuò)展出一個字典的屬性值,如此對于通用統(tǒng)計來說只是看看有沒有某個屬性有的話就扔到統(tǒng)計里桂敛,和業(yè)務(wù)層沒有關(guān)聯(lián)冈绊,但后面帶來的好處很大!這個最后說埠啃。
App生命周期統(tǒng)計
應(yīng)用生命周期的統(tǒng)計隨便建個類,監(jiān)聽下面的通知就ok了伟恶,當(dāng)然這個類就不能銷毀了碴开,比較懶得做法直接AOPLogger 類擴(kuò)展一下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就有了阳液,用的時候類擴(kuò)展一下AOPLogger遵守協(xié)議實現(xiàn)對應(yīng)方法
@protocol AOPLoggerClickProtocol
@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
@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
@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)計,只是舉例...這時候直接寫這個類的類擴(kuò)展load方法里切面然后寫邏輯處理統(tǒng)計晋被,這里不建議用Aspects庫兑徘,底層保證切一個類的一個方法一次是好用的,可業(yè)務(wù)層各種奇葩邏輯都會有羡洛,想解耦經(jīng)常用奇招所以最好允許多次切面挂脑,dispatch_once保證自己的邏輯需要的切一次就可以了,可以用我簡單封裝的方法欲侮。
@interface NSObject (AOPLogger)
+(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)計沙兰,最上層的激活頁面這個是可以在任何地方搞出來的,回頭我可以把類擴(kuò)展放上來翘魄,在任何頁面都可以拿到當(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(手機(jī)網(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)計邏輯