本文主要介绍 火球买手 项目上的埋点方案(基于神策),以及一些心得。事实上在项目早期,咱们的埋点彻底依赖于第三方的全埋点技术,客户端开发人员只须要作一些简单的工做就能知足 BI 部门对数据的需求。但随着业务增加,对数据的准确性和精细化的要求愈来愈高,以后不得不转向手动埋点,固然这个也是基于第三方的。swift
目前 BI 部门对埋点数据要求能够总结为一句话:『从哪里来到哪里去』,好比在 Timeline 中点击一篇文章进入详情页,那么 Timeline 就是『从哪里来』,详情页就是『到哪里去』,固然实际项目中『从哪里来』不仅须要一个维度定位,有时候须要两三个维度才能定位。app
下面具体来讲下 火球买手 项目上是如何埋点的,首先『频道主页』是项目中比较常见的页面,它对应的 Model 是 Channel,而后当任何点击进入的频道主页的事件触发后都须要上报如下数据函数
{ module_name page_name channel_name channel_id }
page_name
指的是当前的 ViewController 名称,module_name
主要用于区分同一个页面内的不一样入口,这样子就能肯定『从哪里来』,channel_name
和 channel_id
数据来自于 Channel,至于『到哪里去』这里就用埋点的 key 来代表,好比是 ChannelClick。code
频道主页在 APP 中入口众多,即便在不考虑埋点的状况下,一个统一的入口也是必要的事件
extension UIViewController { func pushToChannelDetailController(_ id: String?) { // ... } }
显然这样的方法根本没法知足埋点上的需求,改造一下:开发
extension UIViewController { func pushToChannelDetailController(_ model: Channel?) { // ... let value = [ "module_name" : model.module_name, "channel_id" : model._id, "channel_name": model.name, "page_name": self.pageName ] SensorsAnalyticsSDK.sharedInstance()?.track(key, withProperties: value) } }
须要注意的是大多数状况下入口函数只接受一个具象参数是行不通的,由于随着项目的开发业务的迭代总有一些其余的模型被加入,它们一样带有可以跳转至频道主页的 id 属性。还有 pageName 映射:get
extension UIViewController { var pageName: String { switch self { case is ChannelDetailController: return "频道主页" } } }
最后在具体的跳转处设置 module_nameit
@objc func buttonAction(_ sender: Any) { model.module_name = "Header" pushToChannelDetailController(model) }
总体看下来虽然能够应付点击进入频道主页的埋点,可是仍是存在如下问题io
在实际开发中接收不一样的数据模型跳转到同一个页面的状况应该很多见,而且入口函数也是由于埋点把参数从相对抽象的 String 替换成了具象的 Channel,因此抽象 model 是首先要作的。不管接受什么类型参数,传给 ChannelDetail 仍是 id,那么让一个只有带有 id 属性的 protocol 去约束模型再合适不过了。class
protocol CommonModelType { var id: String { get } }
而后让 Channle 遵照这个协议,利用 extension 是为了看起来更解耦
extension Channel: CommonModelType{}
这时候入口函数就是这样
func pushToChannellDetail(_ model: CommonModelType?)
能够接受任何有 id 属性的模型。为了更抽象,甚至可让 String 也遵照这个协议
extension String: CommonModelType { var id: String { return self } }
固然不一样其余能够用来跳转到频道主页的模型均可以这样约束。
埋点数据除了 pageName 不属于 model 之外,其余都属于 model 自己的属性(module_name 属于额外添加),因此和参数同样,一样用 protocol 约束 Channel ,让 Channel 拥有一个直接用于提供数据的属性。
protocol AnalyticsModelType { var analytics: [String: Any] { get } }
让 Channel 同时遵照这两个协议,而且添加 analytics 属性
extension Channel: CommonModelType, AnalyticsModelType { var analytics: [String : Any] { let value = [ "module_name" : module_name, "channel_id" : id, "channel_name": name ] return value } }
最后完整的入口函数是这样的
extension UIViewController { func pushToChannellDetail(_ model: CommonModelType?) { guard let model = model else { return } let viewController = ChannelDetailViewController() viewController.id = model.id navigationController?.pushViewController(viewController, animated: true) guard let value = model as? AnalyticsModelType else { return } var properties = value.analytics properties["page_name"] = pageName SensorsAnalyticsSDK.sharedInstance.track(key: "ChannelClick", properties: properties) } }
总的来讲入口函数够抽象,不管后期增长多少种模型只要它遵循CommonModelType
便可,甚至对于不熟悉项目的人来讲直接传入 id 也是能够正常跳转的。埋点的细节也被隐藏到了入口函数内,而须要上报的数据又由相应的模型负责提供只要它遵循AnalyticsModelType
便可。