一、前言
所谓埋点,是数据采集畛域(尤其是用户行为数据采集畛域)的术语,指的是针对特定用户行为或事件进行捕捉、解决和发送的相干技术及其施行过程,为进一步优化产品或制订有针对性的经营打算提供数据撑持。
埋点的本质,是先监听软件应用运行过程中的要害节点,当须要关注的事件产生时进行判断和捕捉,获取必要的上下文信息,最初将信息整顿后发送至指定的服务端。
神策剖析 iOS SDK,是一款轻量级用于 iOS 端的数据采集埋点 SDK。神策剖析 iOS SDK 不仅有代码埋点性能,还有通过应用运行时机制(Runtime)中的相干技术实现 iOS 端的全埋点(无埋点、无码埋点、无痕埋点、主动埋点)、点击图、可视化全埋点等性能。
其中,代码埋点是最根本也是最重要的埋点形式,实用于须要精准管制埋点地位、灵便的自定义事件和属性等精细化需要的场景。上面针对神策剖析 iOS SDK 代码埋点进行具体的介绍,心愿可能给大家提供一些参考。
二、实现原理
在介绍代码埋点的实现原理之前,咱们先来看下残缺的数据采集流程,心愿大家能够理解代码埋点在数据采集流程中的作用。
2.1 数据采集流程
数据采集流程中次要包含事件采集、增加属性、事件入库、读取上报等流程,具体的步骤如下所示:
在产品、服务转化的某些关键点,调用埋点相干接口采集事件;
获取有意义的属性丰盛该事件,保证数据的广度与深度;
数据采集实现,转换成规范 JSON 数据格式,以队列的模式存储到 SDK 的数据库内;
定时读取数据库中的数据,封装申请并上报数据,并在上报胜利后,删除数据库内存储的已上报数据。
整体流程如图 2-1 所示:
图 2-1 数据采集流程图
从图中能够看出,代码埋点位于数据采集流程的第一步,是数据采集流程中最要害的步骤。数据采集是否丰盛、精确、及时,都间接影响整个数据分析平台的应用成果。
2.2 原理介绍
代码埋点的实现原理比较简单,次要是初始化 SDK 之后,在某个事件产生时调用 – track: 或 – track:withProperties: 等相干接口,将触发的事件和属性保留到数据模型中(SDK 中应用的是 NSDictionary 类型的数据模型)。并将数据模型转化为 JSON 串,存储到本地数据库中。而后,依照发送策略将数据发送到指定的服务端。例如:咱们想统计 App 外面某个按钮的点击次数,能够在这个按钮对应的点击办法外面调用 SDK 提供的接口来采集事件。
三、具体实现
在神策剖析中,咱们应用事件模型(Event)来形容用户在产品上的各种行为,这也是神策剖析中所有接口和功能设计的外围根据。简略来说,一个 Event 就是形容了一个用户在某个工夫点、某个中央、以某种形式实现了某个具体的事件。能够看出,一个残缺的 Event,蕴含如下的几个关键因素:
Who:参加事件的用户是谁;
When:事件产生的理论工夫;
Where:事件产生的地点;
How:用户从事事件的形式;
What:形容用户所做事件的具体内容。
对于 SDK 来说,记录用户行为数据的接口次要思考的就是下面这五个因素。不难看出,接口的次要性能是:在业务特定机会被调用,传入事件名与想要记录的属性或者其余必要参数,而后将事件记录下来。
3.1 接口设计
一个设计良好的接口,应该在输出一组正当的数据时,可能在无限的运行工夫内失去正确的后果;对不合理的数据输出,有足够的反馈和解决能力。参照这个思维,咱们来设计记录用户行为数据的接口。
首先思考接口裸露的局部。开发者在应用一个接口的时候,次要会留神以下几点:
接口名:接口名要足够准确,能用长篇累牍的语言描绘出该接口的性能。针对要实现的性能,咱们把这个接口命名为 – track:withProperties:;
参数列表:通过上述的介绍能够晓得,办法调用机会能够作为事件(Event)的产生工夫(When),此外咱们仍需外界提供的是事件具体内容(What)与从事形式(How),即事件名(参数 event 示意)与事件属性(参数 properties 示意);
返回值:通过该接口记录的用户行为数据最终须要上报到指定的服务端,所以该办法的返回值应合乎指定的服务端所要求的格局。一般来说,数据为 JSON 格局,物理上对应一条数据,逻辑上对应一个形容了用户行为的事件。
基于上述三点,咱们的接口定义如下:
-
(NSString )track:(NSString )event withProperties:(NSDictionary *)properties;
3.2 事件模型的关键因素
通过上述的介绍能够晓得,事件模型(Event)中蕴含五个关键因素,上面就具体介绍下在代码埋点中如何获取这五个关键因素。
3.2.1 用户标识
用户的惟一标识,这里用 distinct_id 示意。简略来说,在用户未登录的状况下,SDK 会选取设施 ID 作为惟一标识,而登录状态下会选取登录 ID 作为惟一标识,即一个用户既有设施 ID(亦称作“匿名 ID”)又有登录 ID,通过“用户关联”能够将同一个用户的设施 ID 和登录 ID 关联到一起。这样,不论用户是匿名状态还是登录状态产生的行为,咱们都能精确辨认到是同一个用户,这是目前为止较为通用且精确的用户标识形式。
1. 设施 ID
大部分状况下,一个用户只有一台设施,因而能够获取其设施的 ID 来作为用户标识。具体到 iOS,咱们可用的是 IDFA、IDFV 或者 UUID。
IDFA:英文全称是 Identifier For Advertising,是广告标识符的缩写,次要用于广告推广、换量等跨利用的设施追踪等。在同一个 iOS 设施上,同一时刻,所有的应用程序获取到的 IDFA 都是雷同的。在 iOS 10 之后,若用户限度了广告追踪(【设置】→【隐衷】→【广告】→【限度广告追踪】),咱们获取到的 IDFA 将是固定的一串零:00000000-0000-0000-0000-000000000000;
IDFV:英文全称是 Identifier For Vendor,是利用开发商标识符的缩写,是给利用开发商标识用户应用的,次要实用于剖析用户在同一利用开发商不同利用间的行为等。在重启设施之后和解锁设施之前,可能获取不到此值;
UUID:英文全称是 Universally Unique Identifier,是通用惟一标识符的缩写,能让你在任何一个时刻,在不借助任何服务器的状况下生成惟一标识符。也就是说,UUID 在某一特定的时空下是寰球惟一的。若 IDFA 和 IDFV 都获取不到,则咱们会生成一个 UUID,作为该设施的 ID。
结合实际状况来看,对于惯例数据分析中的设施 ID,可依照 IDFA → IDFV → UUID 优先级程序获取,基本上能满足咱们的业务需要。
另外,为了避免限度广告追踪、卸载重装等可能导致设施 ID 扭转的状况,SDK 会将设施 ID 存储到 KeyChain 和沙盒中,在肯定水平上防止这个问题。因而,获取设施 ID 的流程如图 3-1 所示:
图 3-1 获取设施 ID 的流程图
2. 登录 ID
个别状况下,在业务后盾零碎中会应用登录 ID 来标识用户,它辨认用户十分精确,然而无奈辨认未登录状态的用户。
在 SDK 中,通过调用 – login: 接口并传入登录 ID,即可实现“用户关联”,将同一个用户的设施 ID 和登录 ID 关联到一起。
3. 惟一标识
在 SDK 中,咱们将设施 ID 定义为 anonymousId,登录 ID 定义为 loginId,用户惟一标识定义为 distinctId。获取 distinctId 的逻辑如下:
若 loginId 不为空且长度不为 0,则返回 loginId;
若 loginId 为空,则返回 anonymousId。
3.2.2 触发工夫
在 SDK 的埋点相干接口中,应用 time 字段来记录事件产生的工夫(单位是毫秒)。若传入的 properties 中不蕴含 time 字段的话,则会主动获取以后工夫作为 time 字段的值,如上面代码所示:
NSNumber timeStamp = @([[NSDate date] timeIntervalSince1970] 1000);
3.2.3 触发地点
能够从三个方面来采集地位信息:
神策零碎会主动依据申请的 ip 来解析相应的省份($province)和城市($city),因而 SDK 并不需要解决这两个属性;
SDK 可通过 CoreLocation 框架主动采集经度($longitude)和纬度($latitude),能够在初始化 SDK 后调用 – enableTrackGPSLocation: 办法进行开启;
开发者也能够设置一些其它地区相干的字段。例如:国家(country)、社区(HousingEstate)等。
3.2.4 从事形式
用户从事这个事件的形式。这个概念比拟宽泛,包含用户应用的设施、浏览器、App 版本、操作系统版本、进入的渠道、跳转过来时的 referer 等。目前,神策剖析预置了局部字段用来形容这类信息,称为预置属性。同时,开发者也能够依据本人的须要来减少相应的自定义字段。
3.2.5 事件内容
形容用户所做事件的具体内容。次要是应用事件名称(event),来对用户所做的内容进行初步的分类。除了 event 这个至关重要的字段以外,咱们并没有设置太多预置字段,须要开发者依据每个产品以及每个事件的理论状况和剖析的需要,来进行具体的设置。
3.3 事件属性
事件触发时除了传入的自定义属性以外,还有一些非凡的属性,可由 SDK 事后采集。例如:页面题目($title)、屏幕宽高($screen_height、$screen_width)等,咱们称之为预置属性。因为这些属性是由 SDK 主动采集的,不须要开发者减少代码,因而极大地减少了数据采集的范畴和便利性。而采集的预置属性自身,是数据分析中波及到的重要剖析纬度,极大升高了开发和采集的老本,是能够拿来即用的局部。
另外,如果所有事件中都须要某些雷同的属性,则能够把这些属性注册为公共属性。
以上两种非凡的事件属性,都能够在肯定水平上节约埋点老本。接下来咱们将介绍这两个属性的实现计划。
3.3.1 预置属性
思考到 SDK 的活跃期基本上可确定为“初始化”与“事件触发”这两个机会,所以预置属性也依据采集机会,大抵分为两类:
SDK 初始化时采集:初始化时即可确定该属性的值,随后在本次 App 生命周期中不会再扭转;
事件触发时采集:调用 – track:withProperties: 时才可确定的属性。
1. 初始化时采集的属性
最容易想到的,也是最优的计划:在 SDK 初始化时创立一个存储属性的模型(能够应用 NSDictionary 类型),命名为 automaticProperties,采集相应属性置入其中,并由 SDK 持有该模型。随后,在每次事件触发时,将该模型中的值增加入属性中即可。采集的预置属性如表 3-1 所示:
表 3-1 初始化时采集的预置属性列表
2. 触发事件时采集的属性
因为一些预置属性,在 App 的整个生命周期中可能发生变化,更强调实时性,因而须要在事件触发时采集,典型代表就是之前已介绍的事件触发工夫(When)与地点(Where)。事件触发时采集的预置属性如表 3-2 所示:
表 3-2 事件触发时采集的预置属性列表
3.3.2 公共属性
有些属性是咱们心愿每个事件都带上,但不属于预置属性,相当于公共的自定义属性。对于这些属性,SDK 提供了两种不同的形式来设置,即“动态”与“动静”公共属性。
动态公共属性在一次 App 生命周期中个别都是固定的;而动静公共属性则相同,只有事件触发的那一刻采集到的值才有意义。这实际上也对应了预置属性的两个采集机会。例如:
利用名称,在一次 App 生命周期中个别都是固定的,因而能够设置为动态公共属性;
以后游戏等级、最新金币余额。显然每次采集时这些值都是变动的,但依然属于公共属性的领域。这时候就能够应用动静公共属性。
1. 动态公共属性
依据下面的剖析,动态公共属性能够这样实现:对外提供一个注册动态公共属性的接口,开发者在 SDK 初始化时通过该接口注册动态公共属性,之后在事件触发时,将动态公共属性增加进去。
依据“在一次 App 生命周期中个别都是固定的”这个特点,动态公共属性存储到内存中即可。然而在实践中,有些动态公共属性在 SDK 初始化时并不能确定,须要通过网络申请或者其余操作后能力被注册。这样也就导致在注册动态公共属性之前的那局部事件,是没有动态公共属性的。如果每次 App 启动后都要反复一遍上述操作,会导致有大量的事件带不上动态公共属性,这显然是有问题的。因而 SDK 也将注册的动态公共属性长久化,并在 SDK 初始化时取出长久化的这部分动态公共属性,提前了动态公共属性的注册工夫,解决了大部分问题。不过,删除动态公共属性时也须要同步革除本地长久化的内容。
注册动态公共属性的代码如下:
[[SensorsAnalyticsSDK sharedInstance] registerSuperProperties:@{@”superKey”:@”superValue”}];
2. 动静公共属性
动静公共属性会在每次事件触发时采集,实用于会常常发生变化的属性。因而,在 SDK 中动静公共属性是通过回调(block)来实现的。残缺的流程如下:
在 SDK 初始化时,或者其余合乎业务的机会,注册回调;
回调中实现属性的采集逻辑,并返回采集的属性;
在事件触发时,调用该回调办法,并将其返回的属性增加到事件属性中。
因为动静公共属性的回调办法在每次事件触发时都会被调用,因而不倡议在该回调办法中增加过多的业务逻辑。注册动静公共属性的代码如下:
[[SensorsAnalyticsSDK sharedInstance] registerDynamicSuperProperties:^NSDictionary<NSString *,id> * _Nonnull{
return @{@”dynamaticKey”:@”dynamaticValue”};
}];
3.3.3 属性的优先级
目前各种属性依照优先级从高到低的排序为:
事件触发时传入的自定义属性;
动静公共属性;
动态公共属性;
预置属性。
不难看出,排序的核心思想是依照“自定义”的优先级来排序:
properties 仅代表本次触发的事件,自定义水平最高;
动静公共属性具备实时性,比动态公共属性优先级高;
预置属性是纯正的 SDK 行为,故优先级最低。
3.4 数据校验
数据校验的内容分为:
参数是否为空、类型是否正确等;
参数是否合乎神策的数据格式要求。神策应用对立的数据格式,因而任何自定义内容都应通过校验来保障输入的 JSON 是符合要求的。具体而言,是对事件名、自定义属性、动态公共属性、动静公共属性等做校验。
数据校验的机会分为:
动态公共属性应在被注册的时候查看;
动静公共属性与自定义属性应在事件触发时查看。
3.4.1 根本限度
事件名(event 的值)和 属性名(properties 中 key 的取值)都需是非法的变量名,即不能以数字结尾,同时只能蕴含:大小写字母、数字、下划线和 $。另外,事件名和属性名最大长度都为 100。上述限度条件在 SDK 中是通过正则表达式来实现的。
SDK 预留了局部字段作为预置事件与属性名,自定义事件与属性都须要防止雷同,判断事件名和属性名是否非法的代码如下所示:
-
(BOOL)isValidName:(NSString *)name {
// 保留字段通过字符串间接比拟,效率更高
NSSet *reservedProperties = [NSSet setWithObjects:@”date”, @”datetime”, @”distinct_id”, @”event”, @”events”, @”first_id”, @”id”, @”original_id”, @”device_id”, @”properties”, @”second_id”, @”time”, @”user_id”, @”users”, nil];
for (NSString *reservedProperty in reservedProperties) {if ([reservedProperty caseInsensitiveCompare:name] == NSOrderedSame) {return NO;}
}
// 属性名通过正则表达式匹配,比应用谓词效率更高
NSString *namePattern = @”^([a-zA-Z_$][a-zA-Z\\d_$]{0,99})$”;
NSRegularExpression *propertiesRegex = [NSRegularExpression regularExpressionWithPattern:namePattern options:NSRegularExpressionCaseInsensitive error:nil];
NSRange range = NSMakeRange(0, name.length);
return ([propertiesRegex numberOfMatchesInString:name options:0 range:range] > 0);
}
3.4.2 类型限度
SDK 的数据类型目前反对五种:数值型、布尔值、字符串、字符串数组、日期工夫,对应到代码中即是 NSNumber、NSString、NSSet、NSArray、NSDate,其它类型的数据将会被回绝。这里须要留神的是:
在 SDK 中,布尔型与数值型一样应用的是 NSNumber 类型。在转为 JSON 后,布尔型的 NSNumber 会被转为 true 或 false,而数值型的 NSNumber 会被转为理论的数值;
NSSet 与 NSArray 都代表数据汇合,只是无序与有序的区别。因而,这两种类型都可代表字符串数组;
NSNull 类型会被独自解决,它不会导致整条数据被抛弃,只会抛弃该键值对。
对于不同类型的属性值,也会有各自独自的校验,如下所示:
NSString:对于字符串,需查看其长度是否大于最大长度 8191。如果大于最大长度,会删掉超出长度的局部,并拼接 $ 代表后续内容已截断。其中,App 解体事件(AppCrashed)的解体起因属性(app_crashed_reason)其值为解体的堆栈,通常都比拟长,故其长度限度设定为惯例值的两倍;
NSSet 与 NSArray:代表字符串数组,会遍历每个对象,查看是否都为 NSString 类型,不是的话会删掉该对象;
NSDate:因为 SDK 数据格式反对的日期工夫理论为 JSON 中固定格局的字符串,所以对于 NSDate,会应用 NSDateFormatter 将其按格局序列化为字符串。
四、应用场景
要理解代码埋点的应用场景,先来看下代码埋点的优缺点,尽量取长补短。
长处:
原理简略,学习老本较低;
应用较为灵便,可能依据业务个性自定义机会、属性、事件,定制化获取数据。
毛病:
埋点老本高,每一个控件的埋点都须要增加相应的代码,不仅工作量大,而且限定了必须是技术人员能力实现;
版本更新前后,容易产生数据错乱;
须要企业长期且稳固地欠缺埋点,并一直依据业务来更新。
根据上述的优缺点能够晓得:代码埋点应用较为灵便,然而老本较高。因而,最好在全埋点、可视化全埋点等埋点计划无奈解决问题时,或者更强调自定义的场景时来应用。例如:
App 的整体日活,App 元素点击的每日次数,可应用全埋点;
App 某个指定按钮的点击事件,某个特定页面的页面浏览事件,可应用可视化全埋点;
若对于业务统计要求十分精确,安全性要求比拟高的用户数据,例如注册、领取胜利,可应用服务器埋点;
以上计划解决不了,或者自定义的内容较多,例如退出购物车、提交订单等,可应用代码埋点。
五、总结
代码埋点是整个神策剖析 iOS SDK 的根底与外围,它足够丰盛稳固,能够让咱们在应用全埋点与存储上报等性能时无后顾之忧。心愿大家通过这篇文章,可能对神策剖析 iOS SDK 的代码埋点有一个全面的理解。
文章起源:公众号神策技术社区