美文网首页iOS 架构设计iOS面试资料搜集
iOS 无痕埋点解决方案——事件 ID 篇(2)

iOS 无痕埋点解决方案——事件 ID 篇(2)

作者: Magic_Unique | 来源:发表于2019-07-24 13:49 被阅读0次

当已经确定了如何通过 AOP 在业务中插入埋点代码后,即可开始采集埋点数据,然后进行上报。

构建的埋点数据可以分为两部分:

  1. 构建一个 Key-Value 数据结构存放此次埋点的数据
  2. 构建一个唯一 ID 用于标识事件,并使用 event_code 作为 key 存放步骤 1 中的数据中

本文主要描述如何生成第二点中的唯一 ID

在下文中,event code 就是事件唯一 ID

要求

用户操作事件埋点,一般用于分析用户行为、用户习惯、某个按钮的日点击量或者时间段点击量等等。为了更便捷的分析这些数据,就会对事件 ID 有一定的要求。

在每次无痕埋点数据采集过程中,都会取到一大堆乱七八糟的数据,而为了准确标识某个用户操作事件,我们必须要有统一的事件唯一 ID 生成方案,而这个方案必须满足以下条件:

  • 同一个界面,同一个按钮,使用一个 ID

不因为当前不同业务数据环境导致 ID 变化,这样有助于大数据分析。

如:使用按钮标题拼接唯一 ID(比如某个按钮标题是当前位置到某个位置的距离,这个距离会根据用户实际位置的变化而变化),这会导致同一个功能,不同的标题产生多个 ID,并且对应同一个事件。

  • 不同界面,或者同一界面的按钮,不能使用相同的 ID

如:使用按钮类名拼接唯一 ID,如果按钮被复用,就有可能导致两个事件的 ID 凑巧相同。

总之,我们要做到事件和 ID 是一一对应的关系,而不是一对多,也不是多对一。

现在,基于上述条件生成唯一 ID。

生成 delegate 埋点的唯一 ID

delegate 埋点一般为下面两种:

  1. -[UITableView tableView:didSelectRowAtIndex:]
  2. -[UICollectionViewDelegate collectionView:didSelectItemAtIndexPath:]

我们 hook 了 -[UITableView setDelegate:] 方法,创建了一个 Proxy 对象作为中间对象,伪装了实际的 delegate,并拦截了对应的点击回调方法。所以我们采集的数据可以在 -[UITableView setDelegate:] 中获取初始数据,以及在 -[Proxy tableView:didSelectRowAtIndex:] 中采集实际点击数据。

采集数据

1. setDelegate: 采集初始化数据

在设置 delegate 时,我们可以拿到 UITableView 的类名(如果被继承了的话),已经业务实际的 delegate 对象。由于这两个数据在 -[Proxy tableView:didSelectRowAtIndex:] 方法中也能拿到,所以此处不会记录这两个数据

2. tableView:didSelectRowAtIndex: 采集实际点击数据

当用户实际点击某一个 Cell 时,会触发此方法。我们可以在此方法中获取非常丰富的数据:

  • self(Proxy 对象)
  • 参数 tableView
    • tableView 可以使用 UIResponse 获取对应的 ViewController
  • 参数 indexPath
    • tableView + indexPath 可以获取对应点击的 Cell 对象
  • self.delegate(业务实际的 delegate)
  • ...

所以在此方法中我们可以至少拿到 6 个数据,接下来进行分析,使用这 6 个数据拼接事件 ID。

拼接事件

首先明确,当我们拿到某个对象时,代表我们可以拿到两个数据:1. 该对象的地址,2. 该对象的类名。由于地址的随机性很大,为了保证上文中的条件,所以不会使用该对象的地址来拼接事件。

Proxy 对象

Proxy 对象是由埋点 SDK 生成的,所以类名一成不变,故 Proxy 对象不能拿来拼接事件 ID。

TableView 对象

TableView 大部分是 UITableView,由于基本不会去继承他,所以不会使用 TableView 的类名。

ViewController 对象

ViewController 一般为自定义的,所以类名也是根据业务实际情况来定,故 ViewController 的类名可以作为唯一 ID 的一部分。

IndexPath 对象

NSIndexPath 是标识行数,由于 TableView 行数可变,不确定。故如果使用 IndexPath 里的数据拼接 ID,将会产生大量不同的名字。所以 IndexPath 对象不能用。但是为了准确标识用户点击了哪一行的 cell,可以将 IndexPath 当做别的参数来上报。

Cell 对象

大部分的 Cell 都是自定义的,所以类名也是根据视图样式来定,故 Cell 的类名可以作为唯一 ID 的一部分。

综述,我们可以拼接 ViewController 和 Cell 来拼接 ID。但是如果一个 VC 中出现了两个 TableView(如外卖 app 的菜单页面),或者近似的两个 TableView。故再加一个 TableView.delegate.className。

最终事件 ID 如下:

VCClassName#DelegateClassName#CellClassName

@implementation MyTableViewDelegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    //  转发给业务
    if ([self.delegate respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]) {
        [self.delegate tableView:tableView didSelectRowAtIndexPath:indexPath];
    }
    
    //埋点
    NSString *event_code = ({
        UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
        NSString *viewController = ({
            UIResponder *responder = tableView;
            while (responder) {
                responder = responder.nextResponder;
                if ([responder isKindOfClass:[UIViewController class]]) {
                    break;
                } else if ([responder isKindeOfClass:[UIWindow class]]) {
                    break;
                }
            }
            NSStringFromClass([responder class]);
        });
        NSString *targetName = NSStringFromClass([self.delegate class]);
        NSString *cellName = NSStringFromClass([cell class]);
        [NSString stringWithFormat:@"%@#%@#%@", viewController, targetName, cellName];
    });
        
    [Tracker trackEvent:event_code];
}

@end

举例:

UIViewController#UIViewController#UITableViewCell

UIViewController#MenuView#MenuCell

生成 Target-Action 埋点的唯一 ID

Target-Action 是手势和 UIControl 的回调,一般使用如下代码

  1. -[UIControl addTarget:action:events:]
  2. -[UIGestureRecognizer initWithTarget:action:]

我们 hook 了 -[UIControl addTarget:action:events:] 方法,创建了一个 Action 对象作为附属对象,和实际的 target 一同添加到 UIControl 中。当 UIControl 触发了事件,就会同时向业务对象和 Action 对象发送消息,从而产生埋点。故我们可以在 -[UIControl addTarget:action:events:] 方法中获取到 target、action、event。还能从 -[Action action:] 方法中获取实时埋点数据。

采集

-[UIControl addTarget:action:events:]

在此方法中,我们可以获取到 UIControl 类名,target 对象,action 方法名,events 事件名。由于后三个数据在下面的方法中无法获取,所以会记录后三个数据到 Action 对象中,供 Action 对象在触发下面的方法时获取对应数据:

//  In UIControl+Hook.m

- (void)hook_addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)events {

    // Call origin method
    [self hook_addTarget:target action:action forControlEvents:events];
    
    //  Create Action object
    MyTargetAction *action = [[MyTargetAction alloc] init];
    action.targetName = NSStringFromClass([target class]);
    action.action = NSStringFromSelector(action);
    action.events = events;

    // Add Action object
    [self hook_addTarget:action action:@selector(action:) forControlEvents:events];
}

-[Action action:]

此方法是实际埋点执行的方法,由于此方法只能获取 self(Action 对象)和 sender(UIControl 对象),故实际埋点数据还是依赖前一个方法临时保存的数据。

此时我们可以在方法中构成埋点数据。

  • self(Action 对象)
  • sender(UIControl 对象)
    • VC(可以根据 UIControl 获取所在 VC)
  • self.targetName(target 类名)
  • self.action(action 方法名)
  • self.events(events 值)

拼接事件

Action 对象

此对象是 SDK 内部对象,无任何信息

sender

控件对象,大部分按钮不会继承,所以也不会有信息。

self.targetName

响应者类名,此类一般为 VC 的类名,或者某个 View 的类名,故此信息可用于拼接。

self.action

响应方法名,于前一个相同,但不同事件一般会有不同方法回调,所以方法名也可以作为唯一事件 ID。

self.events

事件类型,不同控件不同事件,但按钮基本为 UIControlEventsTouchUpInside,如果要区分不同控件则可以加入,本文只考虑按钮情况。故不加入此信息

最终事件 ID 如下:

VCClassName#TargetClassName#ActionName

//  In MyTargetAction.m

- (void)action:(UIControl *)sender {
    NSString *event_code = ({
        NSString *viewController = ({
            UIResponder *responder = sender;
            while (responder) {
                responder = responder.nextResponder;
                if ([responder isKindOfClass:[UIViewController class]]) {
                    break;
                } else if ([responder isKindeOfClass:[UIWindow class]]) {
                    break;
                }
            }
            NSStringFromClass([responder class]);
        });
        [NSString stringWithFormat:@"%@#%@#%@", viewController, self.targetName, self.actionName];
    });
        
    [Tracker trackEvent:event_code];
}

举例:

UIViewController#UIViewController#onClick:

UIViewController#MenuItemCell#onClickAdd:

总结

我们尽可能采集了事件数据,拼接成了事件唯一 ID。事件唯一 ID 的拼接可以根据实际的埋点需求来定,并非一成不变。

以上就是 iOS 端无痕埋点解决方案事件 ID 部分的实现。

在接下来的篇幅中,我将介绍如何在埋点中携带 UI 控件上获取不到的业务数据。

相关文章

  • iOS 无痕埋点解决方案——事件 ID 篇(2)

    当已经确定了如何通过 AOP 在业务中插入埋点代码后,即可开始采集埋点数据,然后进行上报。 构建的埋点数据可以分为...

  • iOS无痕埋点方案分享探究

    iOS无痕埋点方案分享探究 iOS无痕埋点方案分享探究

  • 无痕埋点及上报阿里云日志

    介绍 该项目主要提供了无痕埋点的功能,有关无痕埋点 文章应该有一大堆,这边不做阐述.埋点没有覆盖全部事件,只对一些...

  • 点击事件无痕埋点解决方案--ClickTrace

    背景 最近App开发中遇到了无痕埋点的需求。 所谓无痕埋点,即在App上线后可以上报通用数据(如生命周期事件、点击...

  • 无痕埋点

    一、概念 通过技术手段无差别地记录用户在前端页面上的行为。可以正确的获取 PV、UV、IP、Action、Time...

  • AOP无痕埋点技术

    使用AOP实现iOS应用内的埋点计数 - 简书 iOS用户行为追踪——无侵入埋点 - CSDN博客 iOS 无埋点...

  • iOS可视化埋点(无痕埋点)

    前言 当前互联网行业的竞争已经是非常激烈了, “功能驱动”的时代已经过去了, 现在更加注重软件的细节, 以及用户的...

  • ios 无痕埋点,两种方式

    FNKTrack 两种无痕埋点方式 FNKAopTrack 用Aspects进行AOP无痕埋点,具体见代码. FN...

  • iOS 最优无痕埋点方案

    iOS 最优无痕埋点方案 在移动互联网时代,对于每个公司、企业来说,用户的行为数据非常重要。重要到什么程度,用户在...

  • iOS 无痕埋点解决方案—— AOP 篇(1)

    简单介绍一下 AOP 无痕埋点最重要的技术是将埋点代码从业务代码中剥离,放到独立的模块中的技术。写业务的同学只需按...

网友评论

    本文标题:iOS 无痕埋点解决方案——事件 ID 篇(2)

    本文链接:https://www.haomeiwen.com/subject/hprxrctx.html