iOS 统计打点那些事

标签: ios 统计 打点 | 发表时间:2015-09-09 00:00 | 作者:
分享到:
出处:http://blog.leezhong.com/

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

   // DetailViewController.m

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

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

  1. 页面上会有其他的 Button,可能每个 Button 都要放上这么一段代码。
  2. 这些统计其实跟具体的业务无关,没必要跟业务代码混杂在一起,不优雅。
  3. 当改版或者重构时,有可能忘了把相应的打点代码迁移过去。

所以需要一种更好的方式来做这件事,这就是使用 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];

这样在 UIViewControllerviewWillAppear: 被调用后,还会再调一下我们定义的 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];
}

这样统计代码就从业务代码中剥离出来了。但是又产生了一个新问题,多个 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];
    // ...
}

看起来又干净了些。这时,产品经理又提了个需求:当这个按钮点击时,如果已经登录了,发送 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,被几轮蹂躏后,产品也慢慢地接受了这种验收方式。后来某一天,忽然发现某一项或某几项数据有异常,然后找到开发,瞄了一眼:哦,这个方法被重构了。或者新加的方法忘了加统计了。只能等到下个版本再加上了,如果只是一般的统计数据倒还好,跟钱相关的就麻烦了。

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

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

   // 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 的结构比较复杂时,不过只是内部用来做验证,所以这也不是什么问题。

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

   - (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_getClassListclass_copyMethodList 这两个运行时方法,有了它们就可以在 App 启动时扫一遍已注册的类(过滤掉 UI / NS 开头的),然后将它们的 seletor 也一并保存下来发送给服务端,当然这种操作只需在适当的时机做一下就可以了,比如集成打包时。

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

相关 [ios 统计 打点] 推荐:

iOS 统计打点那些事

- - 无网不剩
统计打点是 App 开发里很重要的一个环节,App 的运行状态、改版后的效果、用户的各种行为等都需要打点,市面上也有不少可供选择的第三方库. 假设产品有这么个需求:当用户在详情页点击购买按钮时,记录一下事件. 这个需求就这样轻松搞定了,但细细想想还是有不少问题的:. 页面上会有其他的 Button,可能每个 Button 都要放上这么一段代码.

9月份操作系统、浏览器统计,iOS移动份额遥遥领先

- xing - cnBeta.COM
统计机构 NetApplications 又例行发布了上个月,也就是2011年9月份的操作系统与浏览器市场份额统计数据. 此次Mac OS在操作系统市场中获得的一年以来最高的增长,使用Mac OS系统的用户已经占到了6.45%.

[IOS]iOS App性能优化

- - 操作系统 - ITeye博客
iOS App的性能关注点. 虽然iPhone的机能越来越好,但是app的功能也越来越复杂,性能从来都是移动开发的核心关注点之一. 我们说一个app性能好,不是简单指感觉运行速度快,而应该是指应用启动快速、UI反馈响应及时、列表滚动操作流畅、内存使用合理,当然更不能随随便便Crash啦. 工程师开发应用时除了在设计上要避免性能“坑”的出现,在实际遇到“坑”时也要能很快定位原因所在.

iOS 5评测

- littlepush - Solidot
Ars Technica的评测认为iOS 5值得升级,当然它也不可避免的存在一些小问题,给用户增添些烦恼. 用户在升级前最好手动备份一下iDevice,确保所有的应用都能转移.

关于iOS 7

- - 曉生
上手使用2天,感觉ios7的方向挺对,有设计的不错的地方,比如系统功能交互的完善和动效细节. 但界面有不够完善之处,比如颜色不够统一,难以理解相机和设置为什么用那么难看的渐变灰色,控制中心太像交互原型图,更主要是功能缺乏分类,但相信这只是beta版的问题,就像ios7运行还不够流畅一样,都还需要时间去完善.

[转]WebKit in iOS 8

- - justinjing的专栏
让我们说说iOS 8 的WebKit吧. WWDC 2014前几天,就有人发现了苹果向WebKit开源项目提交了一些很令人兴奋的代码,暗示了OS X和iOS,特别是iOS上的WebKit架构有所变化. 果不其然,WWDC上公布了iOS的新框架WebKit.framework,正式推出了新的网页浏览控件WKWebView.

Adobe Reader for iOS发布

- laguna - Solidot
tbw 写道 "Adobe也许在计算机桌面的PDF市场占统治地位,但是,Adobe基本上把iPhone和iPad等移动设备的PDF市场留给了竞争对手,如GoodReader和苹果的iBooks. Adobe在公司博客中宣布,它已推出iPhone和iPad通用的“Adobe Reader for iOS”软件.

iOS Web App初步

- - 新浪UED
iOS Web App开发,配合HTML5,是目前比较热门的话题. 今天,先抛开HTML5,我们来尝试在PhoneGap框架上进行简单的开发. PhoneGap是一个使用HTML,CSS和JavaScript的,创建移动跨平台移动应用程序的快速开发平台. 它使开发者能够利用iPhone,Android,Palm,Symbian,WP7,Bada和Blackberry等智能手机的核心功能——包括地理定位,加速器,联系人,声音和振动等,此外PhoneGap拥有丰富的插件,可以以此扩展无限的功能.

iOS开发资源

- - Starming星光社最新更新
iOS App UI 欣赏、分享精美的App界面设计. iOS代码实例搜索、iOS特效示例、iOS代码例子下载. 以web的形式提供iOS UI设计的素材,你可以在web上拖动一些控件做出简单的ios 应用效果,并且生成一个URL,能分享给其他人. 一款 Photoshop 插件,由 UI Parade 推出的一款针对iOS UI 的设计工具,设计师动动鼠标即可制作精美的 iOS 应用原型.

IOS基本概念

- - CSDN博客推荐文章
1)  Objective C的文件扩展名:. 头文件包含类,类型,函数和常数的声明. 这是典型的源代码文件扩展名,可以包含Objective-C和C代码. 带有这种扩展名的源代码文件,除了可以包含Objective-C和C代码以外还可以包含C++代码. 仅在你的Objective-C代码中确实需要使用C++类或者特性的时候才用这种扩展名.