@MicroCai
2016-05-20T12:08:50.000000Z
字数 5199
阅读 6562
Archives
iOS
这两天在搞一个统计模块,把碰到的问题和一些讨论记录下来,所以本文没有答案,没有解决方案,仅是讨论而已。
我现在做的是一个 app 里面的用户行为统计,简单来说就是记录下用户从哪个页面跳转到哪个页面,在页面上都点击了哪些按钮,点击了几次等等之类的东西。
统计工具用的是现成的 Google Analytics,Flurry,MixPanel,我要做的就是将他们集成进我的统计模块代码,并进行各种业务事件的统计。
以 Flurry 为例,当点击登录按钮 clickShootButtonAction
被调用要做一条用户行为统计,统计代码是这样的
- (IBAction)clickShootButtonAction
{
// 执行下面这句话,在 Flurry 的后台就能看到这个事件的记录
[Flurry logEvent:@"点击拍照按钮"];
}
或者
- (IBAction)clickShootButtonAction
{
// 执行下面这句话,在 Flurry 的后台就能看到这个事件的记录
// 与上面不同的是,这边记录的是用户的一条行为路径
// 表示:用户拍照后,在照片分享页,将照片分享到了 Facebook
[Flurry logEvent:@"保存分享照片" withParameters:{@"分享到":@"Facebook"}];
}
而在应用里面记录上百个用户行为是很正常的事情。也就说类似上面的这种代码在 Controller 里面可能要出现几百次,还散落在各处。
如果这些代码遍布我们的工程,使得统计模块和业务代码耦合度极高,造成剥离困难,无法重用等等各种的问题,写起来手累心也累。
对我们来说,最理想的情况下,Controller 里面这种代码越少越好,最好是一行都没有,包含个头文件就能自动统计那该多好。因为如果要剥离统计代码,或者更换统计方式,都是非常方便。
但实际情况不容乐观!!!
根据统计的事件,我们把需要统计的方法大致归类为以下三种,统计剥离的难度也逐级递增
那么问题来了,如果我们不希望在 Controller 里面直接添加统计代码,应该怎么统计上面的三种方法?
这类方法的统计比较简单,写个 UIViewController 的 category,hook UIViewController 的中需要统计的方法,然后将头文件塞到要统计的 ViewController 即可。
这个统计代码的剥离比较麻烦,麻烦的点在于这些方法是根据业务逻辑产生的,每个 ViewController 中的方法都不一致,没法用统一的方式来处理。
关于这类代码的剥离,我查了一些资料,请教一些同学,又在一些技术群讨论了下,确实可以剥离,但方法都不太可靠,所以不建议使用,以下一一列举。
乍看之下,这个思路非常不错,写起来爽歪歪,要添加统计代码时,只要在配置文件中加一下就 OK,嗨到不行。但后来一想这种方式实际执行起来是会有问题的。
首先统计模块代码和业务代码是分散在两个地方,统计是根据具体的类名和方法名的字符串来 hook 后进行统计操作。因为统计模块比较独立,由一个单独的人来写,其他人去写业务代码。业务开发的同学随意修改个方法名还是比较正常的。当业务开发的同学改了个方法名,一般不会想到统计这边也要改;统计这边的同学也不知道业务的同学改了什么。所以这种只有调试时,碰到统计异常才会去检查这里,可维护性太差。
所以这种方法是写的人爽,维护的人非常非常的不爽!
所以这种不推荐使用!
所以这种也不推荐使用!
无解!
想过去真真是无解啊!因为我们前面所用的方法和思路基本离不开 AOP,而 AOP 本身是 hook 一整个方法,在方法前后添加一些自定义的操作。AOP 是没有办法了解方法内部是什么样的,更何谈去统计方法内满足了一定条件再统计事件。
老老实实的将统计代码写到相关的方法里面吧,真没辙了!
折腾了这么些来回,结果还是没法将统计代码和业务代码分开。原以为统计模块应该是一个独立的模块,结果却捅到各个 Controller 代码里面去,实在令人忧伤。
那么到底是什么原因造成了这样一个结局?
回头看看,我们一开始就默认了统计模块是一个独立的模块,应该与业务逻辑分开来。但实际上统计是和业务紧密结合的一个模块,所以是不是可以这么想。统计模块的代码也属于业务逻辑呢?
不管怎样,这么想就可以心安理得的把统计代码写进 Controller 了~
Update:写这篇文章的目的,主要是记录下我的思考过程,虽然结局很忧伤。结果在 CocoaChina 看到有同学吐槽说写了这么多内容,却并没有什么卵用,那我就再更新点内容吧。
研究完代码该怎么写之后,就是实际动手了。在实际写代码的过程中,还有个非常容易碰到的问题 —— 并不是所有的业务逻辑都能在业务代码里面统计到。
写业务逻辑代码的同学,其实是不需要关心统计内容。所以在写业务逻辑的类时,直接将一些行为封装在较为深层的类中,在 Controller 中是没法直接接触这些行为。
这对于明确 Controller 作为控制器的职责,减轻负担,以及深层类的封装性,这么做是相当合理的。但同时带来一个问题,如果要统计深层的用户行为,怎么办?
答案就是:将这些已封装好的类再暴露一些统计需要的接口出来。这在一定程度上破坏了原有类的封装性,但和直接在那些类里面直接写入统计代码相比,来的更加温柔。
所以回到上面的那个问题:统计模块到底是不是属于业务逻辑?我的回答是:统计模块是强烈依赖业务模块的非业务模块,但也可以理解成是一个针对公司内部需要的特殊业务模块。
原则一:千万不要在非业务逻辑处写统计代码
统计代码是和业务逻辑紧密相关的,所以千万不能在非业务逻辑出写统计代码。最好是将统计代码写在 Controller 中,把统计代码写在 Controller 引用的 View 等其他地方都是不太好的做法。
原则二:千万不要在业务代码中引入新的统计相关的变量
尽管统计模块已经深入到各个 Controller,但是统计模块所需的一些变量应该由统计模块自身管理。写统计代码的同学千万不能在 Controller 中引入新的变量或属性,用于记录统计信息。可以建一个类 MCUserBehavior,将需要用的变量放这里,专门用于收集用户行为。
原则三:暂时没有了
有想到的同学可以帮忙补充,谢谢~
原则写起来可能还有一些,但总的一条就是:在需要统计业务的时候,引入一行统计模块的代码就可以做到记录用户行为。
主要有四块:
MCUserDefine.h
这里定义统计的宏
MCUserBehavior
这个类专门用户收集用户行为
MCMixpanel
简单封装了 Mixpanel
MCMixpanelCommonEvent、MCMixpanelShareEvent...
Common Event 用于通用统计,如 [XXX页面]-[下一步去哪儿]-[返回上级页面]
;Share Event 用于特定场景的统计,类似的还有帮助模块 Help Event 等。
这其中最主要的是 MCUserBehavior 类,下面举一个具体的例子说明
业务背景
一款图片处理软件,当用户拍照或者从相册选中一张图片后,进入处理图片的主页,底部有四个 Tabbar 菜单(磨皮、美白、丰胸、滤镜)
用户点击其中任意一个菜单就会进入一个具体的图片处理页面
用户对图片经过一系列的操作(磨皮、美白、丰胸、滤镜),回到主页,点击 [下一步] 进入照片分享页面,或者点击 [取消] 返回上级页面。用户不需要进行所有的图片处理操作,如只做了美白,也可以点击 [下一步] 和 [取消]。
需求:如果用户在主页点击 [下一步],则统计用户进行了哪些操作;点击 [取消] 就不统计。
[用户行为] - [用户进行了哪些操作] - [磨皮]
[用户行为] - [用户进行了哪些操作] - [美白]
[用户行为] - [用户进行了哪些操作] - [丰胸]
[用户行为] - [用户进行了哪些操作] - [滤镜]
现假设
/**
* MCUserBehavior.h
*
* MCUserBehavior 用户收集用户行为
*/
// 用户行为所在的页面
typedef NS_OPTIONS(NSInteger, MCUserBehaviorContext) {
MCUserBehaviorContextViewA = 1 << 0, /**< A 页面的用户行为 >**/
MCUserBehaviorContextViewB = 1 << 1, /**< B 页面的用户行为 >**/
MCUserBehaviorContextViewC = 1 << 2, /**< C 页面的用户行为 >**/
MCUserBehaviorContextViewD = 1 << 3, /**< D 页面的用户行为 >**/
};
// 统计用户使用了多少种图片处理方式
@property (nonatomic, assign) MCUserBehaviorContext userBehaviorTypes;
/**
* AAAController.m
*
* 磨皮页面
*/
// 磨皮方法
- (void)method1111
{
[[MCUserBehavior shareInstance].userBehaviorTypes = 1 << MCUserBehaviorContextViewA];
}
/**
* BBBController.m
*
* 美白页面
*/
// 美白方法
- (void)method2222
{
[[MCUserBehavior shareInstance].userBehaviorTypes = 1 << MCUserBehaviorContextViewB];
}
/**
* CCCController.m
*
* 丰胸页面
*/
// 丰胸方法
- (void)method3333
{
[[MCUserBehavior shareInstance].userBehaviorTypes = 1 << MCUserBehaviorContextViewC];
}
/**
* DDDController.m
*
* 滤镜页面
*/
// 滤镜方法
- (void)method4444
{
[[MCUserBehavior shareInstance].userBehaviorTypes = 1 << MCUserBehaviorContextViewD];
}
/**
* HHHController.m
*
* 主页面
*/
// 点击 [取消],重置用户行为
- (void)method6666
{
[[MCUserBehavior shareInstance].userBehaviorTypes = 0;
}
// 点击 [下一步]
- (void)method5555
{
// recordProcessImageEvent 方法内记录用户用户后,也会重置用户行为
[MCMixpanel recordProcessImageEvent:[[MCUserBehavior shareInstance].userBehaviorTypes];
}
代码中的 MCUserBehaviorContext
类型的用处很大,可以用在多个地方,比如在各个处理页面(如磨皮页面)都有个 [对比]
按钮,如果要用统计各个页面是否使用了对比按钮,也可以复用这个枚举类型:
// 记录用户是否点击了对比按钮
@property (nonatomic, assign) MCUserBehaviorContext userBehaviorCompare;
设计跟着具体业务走的,且我技术水平还是处在较为初级的阶段,所以我的类划分和头文件划分,在其他项目中可能就不太合适了。此处只是提供一个参考和思路,希望能对各位同学有帮助!