iOS打造屬於自己的使用者行為統計系統

來源:互聯網
上載者:User

標籤:instance   響應   遇到的問題   進入   turn   配置資訊   post   fonts   操作   

??打造一款符合自己公司需求的使用者行為統計系統,相信是非常多運營人員的夢想,也是開發人員對技術的的執著追求。

以下我為大家分一享下自己為公司打造的使用者行為統計系統。


??使用者行為統計(User Behavior Statistics, UBS)一直是移動互連網產品中不可缺少的環節,也俗稱埋點。對於產品經理,運營人員來說。埋點當然是越多,覆蓋範圍越廣越好。廢話廢話就不多少了,這裡我主要利用了AOP面向切片編程的思想來解決問題的。參考部落格:參考部落格地址?首先聲明,我這裡並沒有全然照搬別人部落格。這裡主要是順著別人部落格思路去走,走進死胡同,然後返璞歸真,用自己的思路去實現的。

之所以把別人的思路寫下來討論。就是為了說明思考的過程有時也非常重要。

使用者行為統計統計什嗎?

??我們經常說使用者行為統計,那麼使用者行為統計主要統什計麼呢,在我看來主要分為兩類:1,頁面統計:PV2,事件統計:Event

頁面統計:PV

??頁面統計就是就在使用者進入某個頁面的時候。進記行錄儲存。在使用者離開某個頁面的時候進行儲存記錄。

在當適的時候將儲存的資料發送給後台server。實現代碼例如以下:

[UIViewController aspect_hookSelector:@selector(viewDidAppear:) withOptions:JKUBSAspectPositionAfter usingBlock:^(id data){            [self JKhandlePV:data status:JKUBSPV_ENTER];        } error:nil];        [UIViewController aspect_hookSelector:@selector(viewDidDisappear:) withOptions:JKUBSAspectPositionAfter usingBlock:^(id data){            [self JKhandlePV:data status:JKUBSPV_LEAVE];        } error:nil];

非常多部落格貼出這種代碼以為就攻克了問題。事實上忽略了非常大的一個問題,這樣簡粗單暴的去處理,會發現項中目所的有UIViewCnotroller的這兩個方法viewDidAppear:viewDidDisappear:都被會hook,造了成額外的效能開銷,非常的不好。

所以我邊這進行了處理僅僅針對要統的計頁面進行hook操作。具現體執行個體如以下:

+ (void)configPV{    for (NSString *vcName in [[JKUBS shareInstance].configureData[JKUBSPVKey] allKeys]) {        Class target = NSClassFromString(vcName);        [target aspect_hookSelector:@selector(viewDidAppear:) withOptions:JKUBSAspectPositionAfter usingBlock:^(id data){            [self JKhandlePV:data status:JKUBSPV_ENTER];        } error:nil];        [target aspect_hookSelector:@selector(viewDidDisappear:) withOptions:JKUBSAspectPositionAfter usingBlock:^(id data){            [self JKhandlePV:data status:JKUBSPV_LEAVE];        } error:nil];    }}
事件統計:Event

??事件統計主要是在使用者觸發事件時進行記錄儲存,然後在合適的時候將記的錄資料發送給後台server進行處理。

依照文章開頭參考部落格所說,簡單將件事分成了UIButotn,UIControl,UIGestureRecognizer以及點擊UITableView儲存格cell觸發的事件。點擊UICollectionView儲存格cell觸發的事件。
??依照這個思路我首先對UIButton,UIControl觸發的事件進行處理:

+ (void)configUIControlEvent{    [UIControl aspect_hookSelector:@selector(sendAction:to:forEvent:) withOptions:JKUBSAspectPositionAfter usingBlock:^(id<JKUBSAspectInfo> data){        [self JKHandleEvent:data];    } error:nil];}

這個實現起來相對easy些,相信大家都有實現過。

??對UIGestureRecognizer觸發的事件進行處理,比較麻煩 首先UIGestureRecognizer是一個類簇,我們觸發事件時的tap,LongPress,swipe,pan等手勢發送事件是並非發送事件的真正的類。我這邊通過打斷點的形式找到了發送事件的真正的類是:UIGestureRecognizerTarget 發送事件的私人方法是:_sendActionWithGestureRecognizer: 然後我就通過hook操作對手勢觸發的事件進行了處理:

+ (void)configGestureRecognizerEvent{    Class UIGestureRecognizerTarget =NSClassFromString(@"UIGestureRecognizerTarget");    [UIGestureRecognizerTarget aspect_hookSelector:@selector(_sendActionWithGestureRecognizer:) withOptions:JKUBSAspectPositionAfter usingBlock:^(id<JKUBSAspectInfo> data){        [self JKHandleEvent:data];    } error:nil];}

對手勢觸發的事件進行統計儘管困難,但還是實現了。
??對於點擊UITableView儲存格cell觸發的事件,點擊UICollectionView儲存格cell觸發的事件。我這邊以點擊UITableView儲存格cell觸發的事件為例進行說明。假設JKBViewController實現了UITableView 的代理方法tableView:didSelectRowAtIndexPath: 那麼我的實現例如以下:

+ (void)configureDelegateEvent{    [JKBViewController aspect_hookSelector:@selector(tableView:didSelectRowAtIndexPath:) withOptions:JKUBSAspectPositionAfter usingBlock:^(id<JKUBSAspectInfo> data){        [self JKHandleEvent:data];    } error:nil];}

通過這個實現我們能夠做到對點擊UITableView儲存格cell觸發的事件進行統計,可是順著參博考客作者的思路這一步一步做下來。做到這裡我內心有種不的妙感覺。

走進死胡同

以下是參考的部落格作者在開發的過程中遇到的問題

1。並非全部的事件都是有繼承自UIControl的空間來發出的。比方:手勢。點擊Cell。2。並非全部的button點擊了之後就立刻須要埋點上傳?可能在button的回應程式法中經過了層層的if(){ } else{ }最後才須要埋點。

4,對於代理方法該如何處理?5,假設非常多個button相應著一個事件該如何處理?

其針實對第1點,我邊這儘管梳理了非常多類型的事件。可是仍然有非常多沒有被統計上,比方搖一搖觸發的事件。計步器觸發的事件。tabBar點擊觸發的事件等,還非常有多我可能沒到想的事件。我現發假設依照作者的意圖,依照事件觸發的類型去一個一個的進行hook操作的話,工作兩蠻大。並且還是會有遺漏的。尤是其涉及到有方些法蘋果沒有開放給開發人員,我們進行處理的話比較麻煩。

開員發人估被計要累死啊。


針對第2點,按作照者的意圖,會現發點擊之后里面還有層層的推斷。如何繞過層層的推斷呢?這個我會在接下來詳細闡述。
針對第4點。我在上面已經實現過了。
針對第5點,在現實的情況中確實存在者不同的頁面中。甚至同樣的頁面中不同的button相應著同一個事件這種問題。

假設依照參考部落格作者的思路確實處理起來非常是麻煩。

返璞歸真

??針對上面出現的困境。我在想有沒有更好的辦法去解決呢。

首先想到我們統計使用者操的作事件,並是不為了統計使用者點擊了某個button,或者進行了某個手勢操作。調了用某個代理方法。而為是了統計使用者進行這個操作的目的是什麼。是為了購物。還是為了分享等。所以我就打破參考部落格作者的思路,不再對button,手勢。儲存格選中等事件進行hook。而是對使用者的目的事件觸發的方法進行hook,事件就是事件,沒有來源之分。也就是hook就提示的事件。中介層層的邏輯推斷,我不須要考慮。我僅僅考慮hook的目的事件。

舉例個子,使用者要行進分享- (void)goShare;,我不關心用是戶否點擊了button,或者tap手勢觸發了方法,或者儲存格被中選。我僅僅關心分享的方法- (void)goShare;有沒有被調用。被調用的時候我能否夠進記行錄操作。

另外唯一確定一個方法。除了selector,還要有相關的target(方法的實現者,或者訊息接受者)。針上面第5點,不同button相應同一個事件,普通情況下事件同樣target不同,我們是能夠差別的出來的。

當瞭然也存在同一個頁面上的不同button觸發的同一個事件,這種情況下不是太常見,函數外麵包一層。改個別的名字區分一下就好了。只是EnvetID還是要一樣的。


??為了更好的方便大家。我這邊按自照己的思路寫了一個pod庫。以下先說一下自己的plist檔案檔案:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGFuaGFpbG9uZzE4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="這裡寫圖片描寫敘述" title="">
大家能夠看到PV欄位下,每個頁面都以可設定頁面的名字,還一有些其它的資訊。
Event欄位下有EventID,同一時候呢也同意同一個EventID下有不同的觸發事件。


事件1這一級欄位寫上詳細的事件內容,主要是方便開發人讀員閱尋找。


JKVC1點擊,JKVC2點擊,tap單擊。選中tableView儲存格這些都是為了標件來明事源,方便開發人員閱讀。另外假設事件還須要配置額外的參數。那麼能夠在EventID同級欄位下加入新的內容。
下看看面來代碼吧:
JKUBS.h

#import <Foundation/Foundation.h>#import "JKUBSAspects.h"extern NSString const *JKUBSPVKey;extern NSString const *JKUBSEventKey;extern NSString const *JKUBSEventIDKey;extern NSString const *JKUBSEventConfigKey;extern NSString const *JKUBSSelectorStrKey;extern NSString const *JKUBSTargetKey;typedef NS_ENUM(NSInteger, JKUBSPVSTATUS){    JKUBSPV_ENTER = 0,         //進入頁面    JKUBSPV_LEAVE              //離開頁面};@interface JKUBS : NSObject@property (nonatomic,strong,readonly) NSDictionary *configureData;/** 產生單例的方法 @return 單例對象 */+ (instancetype)shareInstance;/** 通過json設定檔匯入配置資訊json設定檔或plist設定檔僅僅匯入一個就好了 @param jsonFilePath json檔案沙箱路徑 */+ (void)configureDataWithJSONFile:(NSString *)jsonFilePath;/** 通過plist設定檔匯入配置資訊json設定檔或plist設定檔僅僅匯入一個就好了 @param plistFileName plist檔案名稱字(不帶尾碼名) */+ (void)configureDataWithPlistFile:(NSString *)plistFileName;/** 處理PV這種方法須要開發人員重載進行詳細的操作 @param data 頁面資訊 @param status 進入或離開頁面的狀態 */+ (void)JKhandlePV:(id<JKUBSAspectInfo>)data status:(JKUBSPVSTATUS)status;/** 處理事件這種方法須要開發人員重載進行詳細的操作 @param data 事件資訊 @param eventId 事件ID */+ (void)JKHandleEvent:(id<JKUBSAspectInfo>)data EventID:(NSInteger)eventId;@end

JKUBS.m

#import "JKUBS.h"NSString const *JKUBSPVKey = @"PV";NSString const *JKUBSEventKey = @"Event";NSString const *JKUBSEventIDKey = @"EventID";NSString const *JKUBSEventConfigKey = @"EventConfig";NSString const *JKUBSSelectorStrKey = @"selectorStr";NSString const *JKUBSTargetKey = @"target";@interface JKUBS()@property (nonatomic,strong,readwrite) NSDictionary *configureData;@end@implementation JKUBSstatic JKUBS *_ubs =nil;+ (instancetype)shareInstance{    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        _ubs = [JKUBS new];    });    return _ubs;}+ (void)configureDataWithJSONFile:(NSString *)jsonFilePath{    NSData *data = [NSData dataWithContentsOfFile:jsonFilePath];    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];    [JKUBS shareInstance].configureData = dic;    if ([JKUBS shareInstance].configureData) {        [self setUp];    }}+ (void)configureDataWithPlistFile:(NSString *)plistFileName{    NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:plistFileName ofType:@"plist"]];    [JKUBS shareInstance].configureData = dic;    if ([JKUBS shareInstance].configureData) {        [self setUp];    }}+ (void)setUp{    [self configPV];    [self configEvents];}#pragma mark PVConfig - - - -+ (void)configPV{    for (NSString *vcName in [[JKUBS shareInstance].configureData[JKUBSPVKey] allKeys]) {        Class target = NSClassFromString(vcName);        [target aspect_hookSelector:@selector(viewDidAppear:) withOptions:JKUBSAspectPositionAfter usingBlock:^(id data){            [self JKhandlePV:data status:JKUBSPV_ENTER];        } error:nil];        [target aspect_hookSelector:@selector(viewDidDisappear:) withOptions:JKUBSAspectPositionAfter usingBlock:^(id data){            [self JKhandlePV:data status:JKUBSPV_LEAVE];        } error:nil];    }}+ (void)JKhandlePV:(id<JKUBSAspectInfo>)data status:(JKUBSPVSTATUS)status{}#pragma mark EventConfig - - - -+ (void)configEvents{    NSDictionary *eventsDic = [JKUBS shareInstance].configureData[JKUBSEventKey];    NSArray *events =[eventsDic allValues];    for (NSDictionary *dic in events) {        NSInteger EventID = [dic[JKUBSEventIDKey] integerValue];        NSArray *eventConfigs = [dic[JKUBSEventConfigKey] allValues];        for (NSDictionary *eventConfig in eventConfigs) {            NSString *selectorStr = eventConfig[JKUBSSelectorStrKey];            NSString *targetClass = eventConfig[JKUBSTargetKey];            Class target =NSClassFromString(targetClass);            SEL selector = NSSelectorFromString(selectorStr);                [target aspect_hookSelector:selector withOptions:JKUBSAspectPositionBefore usingBlock:^(id<JKUBSAspectInfo> data){                    [self JKHandleEvent:data EventID:EventID];                } error:nil];        }    }}+ (void)JKHandleEvent:(id<JKUBSAspectInfo>)data EventID:(NSInteger)eventId{}

當中有兩個方法要重點說一下。

+ (void)JKhandlePV:(id<JKUBSAspectInfo>)data status:(JKUBSPVSTATUS)status。+ (void)JKHandleEvent:(id<JKUBSAspectInfo>)data EventID:(NSInteger)eventId;

這兩個方法都須要在JKUBS的category進行重載,來做詳細的實現。

比如頁面活動的記錄,事件的記錄。打造使用者行為統計系統。我這邊已經完畢了AOP思想下的事件採集。詳細如何記錄,儲存,發給送後台,這裡就不詳細說明了。

代碼
使用pod例如以下:

pod "JKUBS"

注意:demo中我對aspects庫進行了改動。為了防止名字衝突,我這邊統一都加了JKUBS首碼。

歡迎大家來找茬,一塊交流學習。

iOS打造屬於自己的使用者行為統計系統

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.