文档章节

iOS 统计打点那些事

大数据之路
 大数据之路
发布于 2015/09/11 00:40
字数 1770
阅读 4933
收藏 16

1、统计代码埋点真的 so easy?

统计打点是 App 开发里很重要的一个环节,App 的运行状态、改版后的效果、用户的各种行为等都需要打点,市面上也有不少可供选择的第三方库。 假设产品有这么个需求:当用户在详情页点击购买按钮时,记录一下事件。我们实现起来大概会是这样

// DetailViewController.m

- (void)onBuyButtonTapped:(UIButton *)button
{
    // do some stuff, maybe send a request to server
    [XXXAnalytics event:kSomeEventYouDefined];
}

这个需求就这样轻松搞定了,但细细想想还是有不少问题的:

  • 页面上会有其他的 Button,可能每个 Button 都要放上这么一段代码。

  • 这些统计其实跟具体的业务无关,没必要跟业务代码混杂在一起,不优雅。

  • 当改版或者重构时,有可能忘了把相应的打点代码迁移过去。

2、AOP 实现业务代码与统计代码解耦

所以需要一种更好的方式来做这件事,这就是使用 AOP(Aspect-Oriented-Programming),翻译过来就是「面向切面编程」

通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。

简单来说,就是可以动态的在函数调用的前后插一段代码。iOS 可以使用 Pete Steinberger 开发的 Aspects 这个库,大致原理是在 runtime 层,通过 swizzle method 来实现的。

来看一个小 Demo

[UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
    NSLog(@"View Controller %@ will appear animated: %tu", aspectInfo.instance, animated);
} error:NULL];

这样在 UIViewController 的 viewWillAppear: 被调用后,还会再调一下我们定义的 Block,这段日志就会被输出。而打点正好符合这种场景:正事干完之后,额外干一些跟业务无关的事情。

上面的例子,我们通过 AOP 来做的话,大概就是这样

// DetailViewController.m
- (void)onBuyButtonTapped:(UIButton *)button
{
    // do some stuff, maybe send a request to server
    // no need to call [XXXAnalytics event:]
}

// AppDelegate.m
- (void)setupAnalytics
{
    [DetailViewController aspect_hookSelector:@selector(onBuyButtonTapped:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
        [XXXAnalytics event:kSomeEventYouDefined];
    } error:NULL];
}

3、多个 Button 事件怎么办?

这样统计代码就从业务代码中剥离出来了。但是又产生了一个新问题,多个 Button Event,岂不是要写很多行这样的代码,「重复」这样的事情,作为一个程序员怎么能忍,简单,造一个方法

- (void)trackEventWithClass:(Class)klass selector:(SEL)selector event:(NSString *)event
{
    [klass aspect_hookSelector:@selector(selector) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
        [XXXAnalytics event:event];
    } error:NULL];
}

使用起来就像这样

- (void)setupAnalytics
{
    [self trackEventWithClass:DetailViewController selector:@seletor(onBuyButtonTapped:) event:kSomeEventYouDefined];
    [self trackEventWithClass:ListViewController selector:@seletor(followButtonTapped:) event:kAnotherEventYouDefined];
    // ...
}

4、统计事件有选择逻辑呢?

看起来又干净了些。这时,产品经理又提了个需求:当这个按钮点击时,如果已经登录了,发送 EventA,如果没有登录则发送 EventB,也就是说,不再只是 [XXXAnalytics event:] 这么简单了,还需要加上额外的逻辑,这也难不倒我们,加上一个 block 即可。

- (void)trackEventWithClass:(Class)klass
                   selector:(SEL)selector
               eventHandler:(void (^)(id<AspectInfo> aspectInfo))eventHandler
{
    [klass aspect_hookSelector:@selector(selector) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
        if (eventHandler) {
            eventHandler(aspectInfo);
        }
    } error:NULL];
}

// 使用
[self trackEventWithClass:DetailViewController selector:@seletor(onBuyButtonTapped:) eventHandler:^(id<AspectInfo> aspectInfo){
    user.loggedIn ? [XXXAnalytics event:EventA] : [XXXAnalytics event:EventB];
}];

好了,现在只要不是太复杂的打点逻辑(那些需要方法上下文变量的)我们都能应付了,接下来就该等产品来验收了。产品搬了个凳子坐在身边,然后点一下 Button,看一下 Console,被几轮蹂躏后,产品也慢慢地接受了这种验收方式。后来某一天,忽然发现某一项或某几项数据有异常,然后找到开发,瞄了一眼:哦,这个方法被重构了。或者新加的方法忘了加统计了。只能等到下个版本再加上了,如果只是一般的统计数据倒还好,跟钱相关的就麻烦了。

5、如何避免统计代码被遗忘?

那么有没有一种直观的验证方式呢?当然,程序员是万能的呀。一个理想的状况是,产品打开 App 后,开启某个开关就能看到所有会发送 Event 的按钮,就像这样

其中数字代表了 EventID。如何实现呢?还记得注册事件时,我们有传入 class 和 selector 么,一般我们都会有一个 BaseViewController,那么就可以在 BaseViewController 的 viewDidAppear: 里做点文章了。

// BaseViewController.m

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    // 获取已经注册过的 classes
    NSDictionary *registeredClasses = [OurAnalytics sharedInstance].registeredClasses;

    [registeredClasses enumerateKeysAndObjectsUsingBlock:^(NSString *className, NSArray *selectors, BOOL *stop) {
        if ([self isKindOfClass:NSClassFromString(className)]) {
            // 如何根据 selector 找到它的宿主?
        }
    }];
}

所以现在问题就剩下,如何根据 selector 找到对应的 Button,这里要注意,有些 Button 可能要等网络请求完成才会出现,比如 TableViewCell 里的 Button。

没有想到太方便的方法,简单粗暴点就是设置个 Timer 每隔一段时间扫一下 subviews,如果是 button 或 包含 tapGesture 的,就拿它们的 action 对比一下,如果 match 就可以高亮那个 button / view 了。

EventID 也一样,之前在注册时也会传一个 EventID 过来,这里直接显示出来即可。对于那些传 eventHandler 的就不行了。

所以理论上是可行的,性能上会稍微有点损耗,尤其是当 subViews 的结构比较复杂时,不过只是内部用来做验证,所以这也不是什么问题。

6、如何解决 APP 埋码要发版的问题?

看起来效果已经不错了,有没有可能让这套体系再灵活一些?比如可以从后端制定打点规则?客户端只是读取一个配置文件,就像这样

- (void)setupAnalytics
{
    // analyticsRules 是从配置文件中读取出来的
    [analyticsRules enumerateObjectsUsingBlock:^(NSDictionary *rules, NSUInteger idx, BOOL *stop) {
        Class klass = NSClassFromString(rules[@"class"]);
        SEL selector = NSSelectorFromString(rules[@"selector"]);
        NSString *eventID = rules[@"eventID"];
        [self trackEventWithClass:klass seletor:seletor event: eventID];
    }];
}

那如果在后台的时候填错了 Class 或 Selector 怎么办?还好有 objc_getClassList 和 class_copyMethodList 这两个运行时方法,有了它们就可以在 App 启动时扫一遍已注册的类(过滤掉 UI / NS 开头的),然后将它们的 seletor 也一并保存下来发送给服务端,当然这种操作只需在适当的时机做一下就可以了,比如集成打包时。

现在,这套体系就比较完整了。当然这只是我的一些构想,并没有在实践中尝试过,所以肯定会踩到各种各样的坑,不过至少看起来是个可行的方案。

7、Refer:

[1] iOS 统计打点那些事

http://limboy.me/ios/2015/09/09/ios-analytics.html

[2] 基于Aspects和JSPatch的热埋点方案

http://t.cn/RyGQ9wm

[3] 移动APP日志上报优化实践

http://dwz.cn/24BfTW

[4] 天猫App A/B测试实践

http://bit.ly/1pTkxrp

[5] 统计埋点的那些事
http://www.jianshu.com/p/973d626fa19a?from=timeline

[6] 用户行为的深度追踪——事件与埋点

http://www.cnblogs.com/ventlam/p/6414584.html

[7] Android无埋点数据收集SDK关键技术

http://www.jianshu.com/p/b5ffe845fe2d#

本文转载自:http://limboy.me/ios/2015/09/09/ios-analytics.html

大数据之路
粉丝 1601
博文 515
码字总数 333234
作品 0
武汉
架构师
私信 提问
塞班之死---放眼iOS的寒冬

塞班之死---放眼iOS的寒冬 2008年12月2日,塞班公司被收购。2011年12月21日,诺基亚官方宣布放弃塞班(Symbian)品牌 随着论坛衰败,开发者离开,塞班系统已经名存实亡 [图片上传失败...(ima...

_小迷糊
2018/12/26
0
0
好坏美丑,开发者眼中的 iOS 7

跟着新 iPhone 发布的节奏,iOS 史上转变最大的 iOS 7 将要在苹果的各类新产品上大展拳脚了。从消费者眼里看来,这种转变是拟物化向扁平化的转变和新加入的控制中心等。但是对于开发者而言,...

oschina
2013/08/29
5.3K
42
Phonegap各类商业插件

【Phonegap商业插件服务】[目前插件已经支持到Phonegap最新版本] 1.phonegap-百度社会化分享-andriod插件 v2.0 【该插件支持微信分享和朋友圈分享,qq好友分享,微博分享】 2.phonegap百度社...

夜澜小雨
2015/07/15
1K
0
那些在学习iOS开发前就应该知道的事(part 1)

英文原文:Things I wish I had known before starting iOS development—Part 1 设计师设计出来了一个不错的引导界面,然而当我看到设计稿的时候,我们的app也没几天就要上线了。这个界面模...

TomatosX
2015/06/12
99
0
如果只有一个月的时间学 iOS 开发,我们该做些什么?

一年前,我还是一个 Android 开发者。那时候我没有任何 iOS 编程相关的知识,我甚至没用过任何苹果的产品。然而,这已成为过去,现在我已经能同时开发 iOS 和 Android 应用了。 现在,我想给...

oschina
2017/03/09
3.7K
13

没有更多内容

加载失败,请刷新页面

加载更多

哪些情况下适合使用云服务器?

我们一直在说云服务器价格适中,具备弹性扩展机制,适合部署中小规模的网站或应用。那么云服务器到底适用于哪些情况呢?如果您需要经常原始计算能力,那么使用独立服务器就能满足需求,因为他...

云漫网络Ruan
今天
10
0
Java 中的 String 有没有长度限制

转载: https://juejin.im/post/5d53653f5188257315539f9a String是Java中很重要的一个数据类型,除了基本数据类型以外,String是被使用的最广泛的了,但是,关于String,其实还是有很多东西...

低至一折起
今天
23
0
OpenStack 简介和几种安装方式总结

OpenStack :是一个由NASA和Rackspace合作研发并发起的,以Apache许可证授权的自由软件和开放源代码项目。项目目标是提供实施简单、可大规模扩展、丰富、标准统一的云计算管理平台。OpenSta...

小海bug
昨天
11
0
DDD(五)

1、引言 之前学习了解了DDD中实体这一概念,那么接下来需要了解的就是值对象、唯一标识。值对象,值就是数字1、2、3,字符串“1”,“2”,“3”,值时对象的特征,对象是一个事物的具体描述...

MrYuZixian
昨天
9
0
解决Mac下VSCode打开zsh乱码

1.乱码问题 iTerm2终端使用Zsh,并且配置Zsh主题,该主题主题需要安装字体来支持箭头效果,在iTerm2中设置这个字体,但是VSCode里这个箭头还是显示乱码。 iTerm2展示如下: VSCode展示如下: 2...

HelloDeveloper
昨天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部