目录
<a name="前言"></a>前言
用户行为的统计可以帮助我们更好的了解用户的各方面信息。现在比较主流的有两款三方统计库:友盟(国内)和flurry(国外)。但是用户行为收集的代码往往分散在各个类中,难以维护也不雅观,今天交流一种比较好用的用户行为统计方法。本文将全部采用swift来解析。
<a name="准备工作"></a>准备工作
- 三方库
我们需要两个三方库:友盟和Aspects.
* 友盟用来做最终的统计收集
* Aspects 是本文的关键点,它hook住了我们想要的方法,高度集中。
我们采用cocoapods 来管理三方库
platform :ios, '8.0'
use_frameworks!
target 'UserBehaviorSwift' do
#hook
pod 'Aspects', '~> 1.4.1'
#友盟统计(错误分析,事件统计)
pod 'UMengAnalytics-NO-IDFA', '~> 4.0.5'
end
- 桥接(如果是oc项目可以跳过该项)
因这两个库都是oc写的,故我们需要新建一个bribing文件。命名格式最好按照官方建议的 xxx-Bridging-Header.h。记得勾选上targets。如下图
桥接oc头文件.png桥接文件还需要配置路径
桥接文件路径.png
有些三方库的head search(不是所有的库都需要,今天这两个库中Aspects需要配置)
桥接文件时必要配置的三方库.png
桥接代码:
#ifndef UserBehaviorSwift_Bridging_Header_h
#define UserBehaviorSwift_Bridging_Header_h
// 这个库不在 headSearch里面设置就找不到。单独加上
#import "Aspects.h"
//友盟不设置就能找到,应该是framework的缘故
#import <UMMobClick/MobClick.h>
#import <UMMobClick/MobClickSocialAnalytics.h>
#endif /* UserBehaviorSwift_Bridging_Header_h */
- 代码准备
我们需要一个RootVC类和两个继承与RootVC 的A 和 B;一个RootButton.整个项目的代码都已经传到我的github,地址在文章的结尾。
需要的类.png
<a name="页面的hook"></a>页面的hook
- 一般的用户行为收集的写法都是直接在对应的类中写业务。类越多,就写的越多。太过麻烦,难以维护。
override func viewWillAppear(_ animated: Bool) {
MobClick.beginLogPageView("falgA")
}
override func viewWillDisappear(_ animated: Bool) {
MobClick.endLogPageView("falgA")
}
- 我们怎么写?可以用Aspect 巧妙的hook住vc 的生命周期函数。拿viewWillAppear来举例。
//进入页面
let viewWillAppearBlock:@convention(block) (AspectInfo)-> Void = { aspectInfo in
//获得实例
let instance = aspectInfo.instance() as? RootViewController
guard instance != nil else {
return
}
//实例转 格式化 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
//标题
let title = instance?.title != nil ? instance?.title : "unknown"
//最终使用 : type + 类名 + 标题 ,用“/”来分割
let logFlag = "pageIn/" + finalClassName + "/" + title!
MobClick.beginLogPageView(logFlag)
print(logFlag)
}
//转换
let viewWillAppearBlockWrapped: AnyObject = unsafeBitCast(viewWillAppearBlock, to: AnyObject.self)
//最终hook住对应的函数,这里设置了AspectOptions.positionBefore模式,会在viewWillAppear 即将被调用前调用
do {
try RootViewController.aspect_hook(#selector(RootViewController.viewWillAppear(_:)), with: AspectOptions.positionBefore, usingBlock: viewWillAppearBlockWrapped)
}
catch {
print(error)
}
解析
用RootVC hook 住viewWillAppear,所有继承与RootVC的类在每次的viewWillAppear被调用之前都会调用我么已经准备好的block。因是swift 调用oc 故用convention修饰,这里如果直接使用闭包会 crash。block里会返还一个当前调用者的实例,用实例我们可以获得 类名、标题。我们设定了格式来确保这个flag的唯一性质 :进入页面("pageIn/" + finalClassName + "/" + title),离开页面("pageOut/" + finalClassName + "/" + title)。最后我们使用 MobClick.beginLogPageView(logFlag) 对其行为进行收集。
<a name="按钮的hook"></a>按钮的hook
按钮的hook相对于页面来说会复杂一点,多了一些步骤。
//按钮
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)
}
解析
相对于类来说,按钮需要通过alltargets 的一系列格式化才能得到类名。最终格式为("event/" + finalClassName + "/" + title)。因MobClick.event 事件一般都需要产品经理 给你一套他们定制的id,假如你反驳不了的话,那么就好好的享受吧!这里用了plist进行管理,把所有的按钮和对应的id都写进去,用的时候再取出来。
注意: 我么在解析的时候可能碰到一些没有标题或者其他的情况,所有要严格的进行校验,宁愿少记录一条都碍事,也要避免crash.
plist如下图所示
效果图
<a name="总结"></a>总结
实际开发中可能不仅仅需要到 页面和按钮这两种,但都是一样的道理,大多都可以通过这种方法来写,个别写不了的就直接用原始方法写也是无伤大碍。所有的代码都已经传到Github
网友评论