目錄
<a name="前言"></a>前言
用戶行為的統(tǒng)計(jì)可以幫助我們更好的了解用戶的各方面信息。現(xiàn)在比較主流的有兩款三方統(tǒng)計(jì)庫(kù):友盟(國(guó)內(nèi))和flurry(國(guó)外)。但是用戶行為收集的代碼往往分散在各個(gè)類中,難以維護(hù)也不雅觀,今天交流一種比較好用的用戶行為統(tǒng)計(jì)方法。本文將全部采用swift來解析。
<a name="準(zhǔn)備工作"></a>準(zhǔn)備工作
- 三方庫(kù)
我們需要兩個(gè)三方庫(kù):友盟和Aspects.
* 友盟用來做最終的統(tǒng)計(jì)收集
* Aspects 是本文的關(guān)鍵點(diǎn),它hook住了我們想要的方法,高度集中。
我們采用cocoapods 來管理三方庫(kù)
platform :ios, '8.0'
use_frameworks!
target 'UserBehaviorSwift' do
#hook
pod 'Aspects', '~> 1.4.1'
#友盟統(tǒng)計(jì)(錯(cuò)誤分析,事件統(tǒng)計(jì))
pod 'UMengAnalytics-NO-IDFA', '~> 4.0.5'
end
- 橋接(如果是oc項(xiàng)目可以跳過該項(xiàng))
因這兩個(gè)庫(kù)都是oc寫的,故我們需要新建一個(gè)bribing文件。命名格式最好按照官方建議的 xxx-Bridging-Header.h。記得勾選上targets。如下圖
橋接文件還需要配置路徑
有些三方庫(kù)的head search(不是所有的庫(kù)都需要,今天這兩個(gè)庫(kù)中Aspects需要配置)
橋接代碼:
#ifndef UserBehaviorSwift_Bridging_Header_h
#define UserBehaviorSwift_Bridging_Header_h
// 這個(gè)庫(kù)不在 headSearch里面設(shè)置就找不到。單獨(dú)加上
#import "Aspects.h"
//友盟不設(shè)置就能找到,應(yīng)該是framework的緣故
#import <UMMobClick/MobClick.h>
#import <UMMobClick/MobClickSocialAnalytics.h>
#endif /* UserBehaviorSwift_Bridging_Header_h */
- 代碼準(zhǔn)備
我們需要一個(gè)RootVC類和兩個(gè)繼承與RootVC 的A 和 B;一個(gè)RootButton.整個(gè)項(xiàng)目的代碼都已經(jīng)傳到我的github,地址在文章的結(jié)尾。
<a name="頁(yè)面的hook"></a>頁(yè)面的hook
- 一般的用戶行為收集的寫法都是直接在對(duì)應(yīng)的類中寫業(yè)務(wù)。類越多,就寫的越多。太過麻煩,難以維護(hù)。
override func viewWillAppear(_ animated: Bool) {
MobClick.beginLogPageView("falgA")
}
override func viewWillDisappear(_ animated: Bool) {
MobClick.endLogPageView("falgA")
}
- 我們?cè)趺磳懀靠梢杂肁spect 巧妙的hook住vc 的生命周期函數(shù)。拿viewWillAppear來舉例。
//進(jìn)入頁(yè)面
let viewWillAppearBlock:@convention(block) (AspectInfo)-> Void = { aspectInfo in
//獲得實(shí)例
let instance = aspectInfo.instance() as? RootViewController
guard instance != nil else {
return
}
//實(shí)例轉(zhuǎn) 格式化 string
let className = String.init(reflecting: instance)
let arr = className.components(separatedBy: ".")
guard arr.count > 1 else {
return
}
//最終類名
let finalClassName = arr[1].components(separatedBy: ":").first! as String
//標(biāo)題
let title = instance?.title != nil ? instance?.title : "unknown"
//最終使用 : type + 類名 + 標(biāo)題 ,用“/”來分割
let logFlag = "pageIn/" + finalClassName + "/" + title!
MobClick.beginLogPageView(logFlag)
print(logFlag)
}
//轉(zhuǎn)換
let viewWillAppearBlockWrapped: AnyObject = unsafeBitCast(viewWillAppearBlock, to: AnyObject.self)
//最終hook住對(duì)應(yīng)的函數(shù),這里設(shè)置了AspectOptions.positionBefore模式,會(huì)在viewWillAppear 即將被調(diào)用前調(diào)用
do {
try RootViewController.aspect_hook(#selector(RootViewController.viewWillAppear(_:)), with: AspectOptions.positionBefore, usingBlock: viewWillAppearBlockWrapped)
}
catch {
print(error)
}
解析
用RootVC hook 住viewWillAppear,所有繼承與RootVC的類在每次的viewWillAppear被調(diào)用之前都會(huì)調(diào)用我么已經(jīng)準(zhǔn)備好的block。因是swift 調(diào)用oc 故用convention修飾,這里如果直接使用閉包會(huì) crash。block里會(huì)返還一個(gè)當(dāng)前調(diào)用者的實(shí)例,用實(shí)例我們可以獲得 類名、標(biāo)題。我們?cè)O(shè)定了格式來確保這個(gè)flag的唯一性質(zhì) :進(jìn)入頁(yè)面("pageIn/" + finalClassName + "/" + title),離開頁(yè)面("pageOut/" + finalClassName + "/" + title)。最后我們使用 MobClick.beginLogPageView(logFlag) 對(duì)其行為進(jìn)行收集。
<a name="按鈕的hook"></a>按鈕的hook
按鈕的hook相對(duì)于頁(yè)面來說會(huì)復(fù)雜一點(diǎn),多了一些步驟。
//按鈕
let touchesBeganBlock:@convention(block) (AspectInfo)-> Void = { aspectInfo in
let instance = aspectInfo.instance() as? RootButton
guard instance != nil else {
return
}
let className = String.init(reflecting: instance?.allTargets.first)
let arr = className.components(separatedBy: ".")
guard arr.count > 1 else {
return
}
let finalClassName = arr[1].components(separatedBy: ":").first! as String
let title = instance?.titleLabel?.text != nil ? instance?.titleLabel?.text : "unknown"
let logFlag = "event/" + finalClassName + "/" + title!
let path = Bundle.main.path(forResource: "UserBehavior", ofType: "plist")
let dict = NSDictionary.init(contentsOfFile: path!)
let id = dict?.object(forKey: logFlag) as? String
guard id != nil else {
return
}
MobClick.event(id, label: logFlag)
print(logFlag)
}
解析
相對(duì)于類來說,按鈕需要通過alltargets 的一系列格式化才能得到類名。最終格式為("event/" + finalClassName + "/" + title)。因MobClick.event 事件一般都需要產(chǎn)品經(jīng)理 給你一套他們定制的id,假如你反駁不了的話,那么就好好的享受吧!這里用了plist進(jìn)行管理,把所有的按鈕和對(duì)應(yīng)的id都寫進(jìn)去,用的時(shí)候再取出來。
注意: 我么在解析的時(shí)候可能碰到一些沒有標(biāo)題或者其他的情況,所有要嚴(yán)格的進(jìn)行校驗(yàn),寧愿少記錄一條都礙事,也要避免crash.
plist如下圖所示
效果圖
<a name="總結(jié)"></a>總結(jié)
實(shí)際開發(fā)中可能不僅僅需要到 頁(yè)面和按鈕這兩種,但都是一樣的道理,大多都可以通過這種方法來寫,個(gè)別寫不了的就直接用原始方法寫也是無傷大礙。所有的代碼都已經(jīng)傳到Github