非侵入式自动采集iOS数据的探索和实践

优采云 发布时间: 2020-08-07 11:05

  随着库存应用程序的不断迭*敏*感*词*发,数据和业务部门对客户用户行为数据的要求越来越高;为了更好地监视应用程序的使用状态,客户团队拥有有关应用程序本身操作的数据. 需求变得越来越迫切. 迫切需要一套客户端数据采集工具,以自动,全面地采集用户行为数据,以满足各个部门的数据需求.

  有货APP团队为此目的开发了一套数据采集SDK. 主要功能如下:

  页面访问流程. 用户在应用程序中浏览了哪些页面.

  浏览数据. 用户在页面上查看了哪些产品.

  自动采集业务数据. 用户在应用程序中点击了哪些位置以及触发了哪些操作.

  自动采集性能数据. 在用户使用APP的过程中,页面加载时间,图像加载时间,网络请求时间等是什么?

  此外,所有数据采集都应该是自动化且非侵入性的,也就是说,无需手动掩埋点,并且可以集成SDK,并且不应更改原创代码或进行尽可能少的更改

  基于上述要求,AOP是技术解决方案的最佳选择,并且在iOS上实现AOP需要依靠Objective-C-Method Swizzle中运行时的黑魔法. 踏入坑和填充坑的漫长旅程从这里开始,让我们逐一品尝实现的想法和方法.

  页面访问流程

  用户访问页面统计信息需要解决两个问题:

  统计事件的入口点,即何时计数.

  统计数据字段,即计算什么数据.

  整个过程如下:

  

  统计事件的切入点

  用户访问页面统计信息的一般思想是在View Controller生命周期方法中:

  可以获得用户访问页面的路径,这两个事件时间戳之间的差是用户停留在页面上的时间.

  通常,我们APP中的View Controller继承自某个基类. 我们可以在基类的相应方法中执行统计. 但是,对于不继承自基类的View Controller,我们无能为力.

  借助AOP,我们可以更优雅地做到这一点: 在UIViewController的load方法中摇动viewDidAppear和viewDidDisappear方法,无需更改原创代码.

  统计信息字段

  根据数据要求,设置以下统计字段:

  页面进入和退出的事件都报告给上述数据结构.

  还有几个问题需要考虑:

  1. 如何定义PAGE_ID和SOURCE_ID

  由于您需要统一iOS和Android的PAGE_ID,因此需要对其进行配置和发送. iOS端得到的是一个plist文件,该文件的键是View Controller类名的字符串表示形式,值是PAGE_ID.

  2. 如何获取PAGE_ID和SOURCE_ID

  可以根据当前View Controller的类直接获取

  PAGE_ID. SOURCE_ID稍微复杂一些. 需要根据APP页面的嵌套堆栈结构来确定具体的获取方法. 通常,上一个View Controller的页面取自UINavigationController id的导航堆栈.

  到目前为止,页面访问流统计信息已基本完成. 根据页面进入和退出的PAGE_ID和SOURCE_ID,输入完整的用户浏览路径,并获得用户在每个页面上的停留时间.

  浏览数据暴露

  在采集了用户的浏览路径以及在某些页面(例如主页和产品列表页面)上的每一页上花费的时间之后,我们还想知道用户在该页面上滑动了多少屏幕,以及他们观看了哪些屏幕活动,产品,以便更好地为用户推荐喜爱的产品.

  用户看到的屏幕区域被视为资源位,因此用户看到的内容由资源位组成. 那么暴露的含义如下:

  我们知道iOS中页面元素的基本单位是视图,因此我们只需要判断视图是否在可见区域中,然后就可以知道当前视图上的资源位置是否需要公开,以便进行相应的曝光操作并捕获数据,报告界面等.

  从以上分析可以看出,有两个主要问题需要解决:

  查看可见性判断

  查看曝光数据采集

  查看可见性判断

  查询UIView类参考以查看setFrame: 和layoutSubivews方法,这些方法可用于设置子视图的框架. 每次更新观看次数时,都会调用此方法. 因此,我们可以通过运行时选项卡来实现此方法,并添加一些与数据采集相关的操作.

  我们向UIView添加了以下属性:

  首先阐明以下术语的定义和规则:

  1.view的子视图可以同时看到3个需要满足的条件:

  相反,只要不满足上述任何条件,我们认为此子视图当前不可见.

  2. 将视图设置为可见

  3. 将视图设置为不可见

  Swzzile setFrame :,请执行以下操作:

  

  易用的layoutSubivews,调用yh_updateVisibleSubViews方法,该方法执行以下操作:

  

  完成上述操作后,我们可以知道一个视图及其子视图是否可见.

  查看曝光数据采集

  为了获取与视图相对应的数据,还将以下属性添加到UIView:

  然后还有两个问题:

  浏览量数据的粒度

  视图及其子视图节点的曝光数据的组装时间

  浏览量数据的粒度

  根据项目的实际经验,UITableViewCell或UI采集ViewCell通常是最小的粒度. 同时,在最后一个节点的yh_exposureData字典中添加一个键: isEnd,以标识它是否是最后一个节点.

  组装视图及其子视图的曝光数据的时间

  通常,当最后一个节点的可见性发生变化时,请从下到上遍历最后一个节点的超级视图以组装所有数据.

  因此,我们覆盖了setYh_viewVisible: 方法,这是yh_viewVisible的set方法. 请执行以下操作:

  到目前为止,我们已经解决了视图可见性判断和曝光数据采集的问题. 数据报告和策略将不会重复.

  此方案有几个缺点

  您需要手动设置曝光数据.

  您需要在适当的时间手动调用view.yh_viewVisible来触发数据采集,例如viewdidappear.

  它需要消耗某些资源才能进行视觉区域计算和曝光数据采集.

  还有两个问题值得注意:

  UITableView将在setBounds: 时更改视图框架,因此您需要使用setBounds: 方法,需要调用[self yh_updateVisibleSubViews];

  UIScrollView会在setContentInset: 时影响视图的可见区域,因此需要调整setContentInset: 方法,需要在设置contentInset之后调用self.yh_viewVisibleRect = UIEdgeInsetsInsetRect(self.frame,contentInset);

  自动采集业务数据

  自动采集业务数据是一种流行的数据采集,没有行业中的隐患.

  传统的客户端用户点击数据采集基于手动隐藏点,您对数据感兴趣的地方,只需单击此处,然后在用户操作后立即触发数据报告. 手动掩埋的缺点很明显: 错误的掩埋和丢失的掩埋. 新版本发布后,经常有来自数据部门的小伙伴报告说,某些点的问题尚未报告,某些点已报告错误,开发人员也很痛苦.

  没有埋藏点的数据采集带来了新的变化. 首先,基本上避免了人工掩埋,个别情况需要特殊处理. 其次,从有选择的数据采集中,它变成了所有用户点击和触摸数据的完整采集.

  新变化也将带来新挑战. 非埋入点数据采集的可能性仍基于Objective-C的运行时特征. 在实践过程中,我们使用iOS非埋入点数据SDK的总体设计和技术实现作为参考,并使用Sensors Analytics iOS SDK和Mixpanel iPhone作为实现. 接下来,结合特定的实践,我们将介绍实现思想和遇到的一些问题. 主要分为以下三个方面:

  如何确保自动采集的点的唯一性.

  不同的点类型,毛病需要哪些方法.

  在大雨中进站了.

  如何确保自动采集的点的唯一性

  自动采集与手动埋入点是分开的,因此没有唯一的标识点. 那么,我们如何唯一地定位自动采集的点呢?一个容易想到的解决方案是: 基于页面视图的树结构. 该解决方案可以分为两个问题:

  如何定义视图的唯一标识符.

  视图如何唯一标识.

  视图唯一标识符(视图路径)的定义

  我们规定典型的查看路径如下:

  ViewController[0]/UIView[0]/UITableView[0]/UITableViewCell[0:2]/UIButton[0]

  其中:

  使用此标识符,可以在当前页面的视图树结构中唯一标识此元素.

  每个项目由

  标识

  由两部分组成: 一个是当前元素类的字符串表示形式,另一个是同一级别元素中当前元素的序列号,从0开始计数. 例如,当前的第二个UIImageView是UIImageView1.

  标识不同项目之间的联系.

  识别出的最高层是当前视图所在的ViewController.

  对于UITableViewCell,UI采集ViewCell和类似的自定义组件,序列号部分由两部分组成: 节和行,它们被拼接在一起.

  标记的末尾是当前被单击或触摸的元素.

  如何生成视图唯一标识符

  视图路径生成过程: 从触发操作的最末端元素向上查询,直到找到ViewController. 假设当前单击的视图是A_View,则从当前A_View开始遍历视图树,并将每个级别的数据存储在P_Array中. 过程如下:

  

  如果A_View是UI采集ViewCell类型,请获取A_View所在的UI采集View的indexPath,P_Array推送路径信息[NSString stringWithFormat: @“%@ [%ld: %ld]”,[NSString stringWithFormat: @“%@”, NSStringFromClass([A_View class])],(long)indexPath.section,(long)indexPath.row];

  如果A_View是UITableViewCell类型,请获取ATable所在的UITableView的indexPath,P_Array推送路径信息[NSString stringWithFormat: @“%@ [%ld: %ld]”,[NSString stringWithFormat: @“%@” ,NSStringFromClass([A_View class])],(long)indexPath.section,(long)indexPath.row];

  遍历A_View.superview的所有子视图,获得相同级别的A_View,以及相同类型的数字(索引)([A_View类]),P_Array推送路径信息[NSString stringWithFormat: @“%@ [%d ]“,NSStringFromClass([A_View class]),index];

  获取A_View所在的控制器A_VC. 如果A_View为A_VC.view,则遍历结束. 如果A_View不等于A_VC.view,则A_View = A_View.superview,重复步骤1-4,直到A_View等于A_VC.view.

  遍历P_Array拼接A_View的完整路径.

  各种类型的点都需要Swizzle方法

  我们将APP中的用户操作分为四类:

  UI采集View和UITableView的单元格单击事件.

  UIControl(UISwitch,UIStepper,UISegmentedControl,UINavigationButton,UISlider,UIButton)控件的click事件.

  UIImageView和UILabel上的UITapGestureRecognizer触摸事件.

  UITabBar,UIAlertView,UIActionSheet等的点击事件.

  这四种操作都需要使用swizzle方法,如下表所示:

  

  UI采集View,UITableView,UITabBar,UIAlertView和UIActionSheet的实现是相似的. 它们都是load方法中的swizzle setDelegate方法. 在setDelegate之后,执行代理回调方法的swizzle操作. 在回调方法中,首先执行原创逻辑. ,然后获取相应的viewPath.

  当UIControl组件回调到目标时,将由UIApplication的sendAction: to: from: forEvent: 调用,因此我们选择swizzle方法. 在实践中,首先获取相应的视图路径,然后执行原创逻辑. 原因是如果首先执行原创逻辑,则页面可能会更改,并且获得的View Controller将是错误的.

<p>UITapGestureRecognizer事件仅在UIImageView和UILabel上处理. swizzle addGestureRecognizer: 方法,首先执行原创逻辑,然后向视图添加自定义回调方法,以便在触发手势时也将调用自定义回调,此时我们获得了视图路径.

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线