埋点现状
- 乱且无规范,所有埋点事件在一个文件里,属于什么业务需要看一层层调用
- 参数多且多层传递,不好维护和修改
比如:
若该埋点需要增加一个字段,至少需要修改这三个地方,比较耗时;且必须花费一些时间去理解每一层做了什么;徒增开发成本
目的:
增强埋点与业务的联系,便于管理
减少参数传递,减少代码重复,维护埋点代码质量
埋点方案
规范:公共协议 Report,定义了上报事件必需的属性,另外定义了函数来获取最终上报的数据结构
/// 上报平台类型
enum ReportPlatform {
case sensor // 神策
case zeus // 宙斯 绝大部分埋点两个平台同时上报
case appsFlyer // 部分旧埋点有上报,但新事件没有
}
protocol Report {
/// 事件名
var eventName :String { get }
/// 事件属性
var properties: [String : Any] { get }
/// 上报平台
var platform: [ReportPlatform] {get}
/// 获取上报数据结构
func getReportableData() -> [String : Any]
}
基础上报模型 ReportBaseModel,遵循Report协议,所有埋点事件的上报数据模型都组装为它的子类;该模型提供默认的上报平台,实现获取上报数据结构函数
struct ReportBaseModel: Report {
/// 上报数据结构key:事件名
static let eventIdKey = "eventId"
/// 上报数据结构key:事件属性
static let eventInfoKey = "data"
/// 子类应重写事件名
/// 每个埋点事件创建单独的上报模型,提供唯一的事件名
var eventName :String {
""
}
/// 子类应重写事件上报的字段
var properties: [String : Any] {
["" : ""]
}
/// 默认上报神策,宙斯
var platform: [ReportPlatform] {
[.sensor, .zeus]
}
/// 获取上报数据结构
/// - eventId : 事件名
/// - data: 事件的属性/字段
/// - time: 上报时间
/// - Returns: 最终上报到平台的字典结构
func getReportableData() -> [String : Any] {
let reportData: [String : Any] = [Self.eventIdKey: self.eventName,
Self.eventInfoKey : self.properties,
"time" : "\(Int(Date().timeIntervalSince1970))"]
/// 检查字典转Json是否成功,提前暴露properties类型问题
DSReporter.checkProperties(properties: self.properties)
return reportData
}
}
统一上报实现
通过model的platform判断需要上报到哪些平台
class DSReporter {
/// 检查字典转Json,测试环境直接报错;可以知道哪个埋点事件报的错,从而找出属性值赋值错误
static func checkProperties(properties: [String : Any]) {
#if DEBUG
try? _ = JSONSerialization.data(withJSONObject: properties, options: .prettyPrinted)
#endif
}
/// 根据模型的上报平台,上报事件
static func reportEvent(_ model: Report) {
let reportData: [String : Any] = model.getReportableData()
/// 上报神策
if model.platform.contains(.sensor) {
DSReportClient.shared.submitReport(reportData)
}
/// 上报宙斯
if model.platform.contains(.zeus) {
DSReportClient.shared.submitSensorReport(reportData)
}
/// 上报AppsFyler
if model.platform.contains(.appsFlyer) {
AppsFlyerLib.shared().logEvent(model.eventName, withValues: nil)
}
}
}
埋点事件上报
每个埋点事件创建独有的上报数据模型,该模型继承ReportBaseModel,在模型内部维护事件名eventName,处理并组装成事件属性properties;然后在业务通过DSReporter.reportEvent(model)上报事件
-
普通埋点【单处业务调用】,在业务模块处建立Report文件夹,该业务的相关埋点事件都放在该文件夹中;为每个埋点事件创建继承ReportBaseModel的上报数据模型
/// 定义上报数据模型,继承ReportBaseModel // DSTabSwitchReportModel.swift class DSTabSwitchReportModel: ReportBaseModel { var tabInfo: DiscoverTab = DiscoverTab() /// 0 默认选中 1 用户主动选中 var isClick: Bool = false /// 重写事件名 override var eventName: String { return "tabSwitch" } /// 重写事件属性字段 override var properties: [String : Any] { let tabTypeValue = tabInfo.type == .tabTypH5 ? "H5" : "regular" let type = isClick ? 1 : 0 let params: [String : Any] = [ "tab_id" : tabInfo.id, "tab_name" : tabInfo.name, "tab_type" : tabTypeValue, "type" : type, ] return params } }
/// 业务上报处 func segmentedView(_ segmentedView: JXSegmentedView, didSelectedItemAt index: Int) { guard let homeModel = homeModel else { return } let tab = homeModel.tabList[index] let tabSwitchReportModel = DSTabSwitchReportModel() tabSwitchReportModel.tabInfo = tab tabSwitchReportModel.isClick = isUserClick DSReporter.reportEvent(tabSwitchReportModel) // 调用通用上报 }
-
其中有些业务的多个埋点事件属性有较多相同的属性,提取成这些埋点事件的公共基类,这样无需为每一个类似的埋点属性进行数据处理和构建字典,可大大减少重复代码
比如:
-
通用埋点【多处业务调用】
- 通用上报文件夹,通用埋点事件上报模型都放在该文件夹中,每个通用埋点事件创建上报模型,相关类型放在对应上报模型文件中
书籍曝光、上报、点击
按钮点击
浮窗曝光
比如:
- 参数只有一个或者没有的埋点事件,在埋点上报模型中提供快捷上报方法,简化上报
比如按钮点击:
新旧方案埋点对比分析
同一个埋点事件新旧埋点方式实现对比
旧埋点方案
// 原上报方式如下:传递三层参数,第一层业务上报处,第二层处理参数,第三层处理成上报字典中的data字段
/// 业务上报处
func segmentedView(_ segmentedView: JXSegmentedView, didSelectedItemAt index: Int) {
guard let homeModel = homeModel else { return }
currentTabId = homeModel.tabList[index].id
let tab = homeModel.tabList[index]
DSReportClient.reportTabSwitch(tabIndex: index, tabId: currentTabId, tabName: tab.name, tabType: tab.type, isShowImage: false, isClick: isUserClick)
}
// 处理参数: DSReportClient.swift
/// 发现页tab-探索页 tab切换 埋点
static func reportTabSwitch(tabIndex: Int, tabId: Int32, tabName: String, tabType: DiscoverTabType, isShowImage: Bool, isClick: Bool = false) {
let type = isClick ? 1 : 0
let tabTypeValue = tabType == .tabTypH5 ? "H5" : "regular"
let eventObj = DSReportConst.TabSwitchEvent(tabIndex: tabIndex, tabId: tabId, tabName: tabName, tabType: tabTypeValue, is_image_show: isShowImage, type: type)// 处理数据处
DSReportClient.shared.submitReport(eventObj) // 上报处
}
/// 返回上报字段:DSReportConst.swift
/// 发现页tab-探索页 tab切换
static func TabSwitchEvent(tabIndex: Int, tabId: Int32, tabName: String, tabType: String, is_image_show: Bool, type: Int) -> [String: Any] {
let data: [String: Any] = [
"tab_id" : tabId,
"tab_name" : tabName,
"tab_type" : tabType,
"is_image_show": is_image_show,
"type" : type,
]
return baseEvent(id: "tabSwitch", data: data)//
}
新埋点方案
/// 定义上报数据模型
class DSTabSwitchReportModel: BaseReportModel {
var tabInfo: DiscoverTab = DiscoverTab()
/// 0 默认选中 1 用户主动选中
var isClick: Bool = false
override var eventName: String {
return "tabSwitch"
}
override var properties: [String : Any] {
let tabTypeValue = tabInfo.type == .tabTypH5 ? "H5" : "regular"
let type = isClick ? 1 : 0
let params: [String : Any] = [
"tab_id" : tabInfo.id,
"tab_name" : tabInfo.name,
"tab_type" : tabTypeValue,
"type" : type,
]
return params
}
}
/// 业务上报处
func segmentedView(_ segmentedView: JXSegmentedView, didSelectedItemAt index: Int) {
guard let homeModel = homeModel else { return }
currentTabId = homeModel.tabList[index].id
let tab = homeModel.tabList[index]
// 上报tab切换
let model = DSTabSwitchReportModel()
model.tabInfo = tab
model.isClick = isUserClick
DSReporter.reportEvent(model)
}
- 无需多层传递参数
- 不需为每个埋点事件写相同的属性处理代码
- 埋点事件的事件名,事件属性在模型内部统一管理,外部无需关心