非侵入式自动采集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: 方法,首先执行原创逻辑,然后向视图添加自定义回调方法,以便在触发手势时也将调用自定义回调,此时我们获得了视图路径.