Skip to content

27统计分析:如何架构灵活的统计分析服务,助力产品增长?

App 上架是产品发展的一个里程碑,同时也是产品长远发展的起点。那有没有什么方法能帮助我们持续地改善产品功能与用户体验,并制定产品发展的方向与目标呢?当然有!我们可以使用统计分析服务(Analytics)。

统计分析服务能为我们提供用户交互度指标(Engagement Metrics),这些指标包括用户会话的活跃度、活跃的设备类型以及用户留存率等。 有了这些指标,我们就能衡量每次发布的新功能是否能有效提高用户的交互频率,从而及时调整产品发展的方向。

市面上有许多统计分析服务,为了方便使用各种不同的服务,我们为 Moments App 架构了一套灵活的统计分析模块,同时使用 Firebase 作为例子来演示如何支持一种统计分析服务。之所以选择 Firebase,其主要原因有如下三个:

  1. Firebase 功能齐全,除了统计分析服务以外,几乎包含了我们优化 App 所需的各种服务,例如崩溃报告、远程配置与遥控功能开关、App 分发服务、A/B 测试等;

  2. 免费版的 Firebase 足够使用,与功能相当的收费产品相比,能省下不少钱;

  3. Firebase 配置方便,只需搭建一次就能长久使用。

配置 Firebase 服务

在讲述如何使用 Firebase 的统计分析服务前,我们先看看如何为 Moments App 配置 Firebase 的服务。

1. 创建项目

首先,我们登录到 Firebase 网站来新建一个项目。请注意,在新建的时候必须选择"Enable Google Analytics for this project"(为该项目启动统计分析服务)选项,否则将没办法使用 A/B 测试等一系列的服务。

然后在新项目里添加 App,我的做法是为开发环境、测试环境和生产环境各自添加不同的 App。下图演示了如何添加测试环境的 Internal App。

这里关键是要填写正确的 Bundle ID。你可以到各个 Target 的 xcconfig 文件里面分别找到它们的 Bundle ID,例如在 InternalTarget.xcconfig 文件里面有如下的定义:

java
PRODUCT_BUNDLE_IDENTIFIER = com.ibanimatable.moments.internal

2. 下载 Firebase 配置文件

第二步是为三个环境的 App 分别下载 Firebase 的配置文件。当我们把三个配置文件下载完毕后,为了方便管理,可以使用"GoogleService-Info-<环境名称>.plist"的命名方式来改名,然后把所有的配置文件都拖到 Moments 项目里面,如下图所示:

同时在各个 Target 的 xcconfig 文件分别添加名叫FIREBASE_CONFIG_FILENAME的 Build Setting,如下所示:

java
FIREBASE_CONFIG_FILENAME = GoogleService-Info-Development

接着在 Build Phases 上添加"Copy Firebase Config File"步骤的配置信息,并输入下面的脚本:

java
cp "${PROJECT_DIR}/Moments/Configurations/Firebase/${FIREBASE_CONFIG_FILENAME}.plist" "${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}/GoogleService-Info.plist"

具体配置如下图所示:

有了这个步骤的配置,Xcode 在构建的过程中就会执行。因为步骤配置里的 Shell 脚本中使用了 xcconfig 里面的FIREBASE_CONFIG_FILENAME变量,所以当我们构建不同环境的 App 时,Xcode 会自动拷贝对应的 Firebase 配置文件到不同的 App 里面,这样就使得不同的 App 能把用户事件发送到不同的统计分析数据服务,进而保证生产环境的数据不会受到污染。

3. 安装 Firebase SDK

下载完配置文件以后,下一步是安装 Firebase SDK。官方推荐的方式是使用 CocoaPods 来安装 Firebase SDK。我们只需要把 Firebase 添加到 Podfile 即可,具体代码如下:

java
def thirdparty_pods
  pod 'Firebase/Analytics', '= 7.0.0'
end
target 'Moments' do
  ...
  thirdparty_pods
  ...
end

我们通过pod命令来添加统计分析服务,然后把这些 Pod 通过thirdparty_pods函数添加到 Moments target 里面,最后重新执行bundle exec pod install命令就能完成 Firebase SDK 的安装了。

4. 初始化 Firebase 服务

安装完 Firebase SDK 后,我们还需要在 App 里面进行初始化 Firebase 服务,只需要两步,具体代码如下:

swift
import Firebase // 1
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        FirebaseApp.configure() // 2
        return true
    }
}

第一步是通过import Firebase语句来引入 Firebase 库,第二步是在application(_:didFinishLaunchingWithOptions:)函数里调用FirebaseApp.configure() 函数来启动 Firebase 服务。

好了,至此我们就为 Moments App 配置好 Firebase 服务了。

统计分析模块

下面我们以 Moments App 为例子,看看如何架构与实现一个灵活的统计分析模块以及如何使用该模块。

1. 统计分析模块的架构与实现

首先,我们看一下统计分析模块的架构图,如下图所示:

这里我们从右往左看,根据依赖关系,统计分析模块由用户活动事件(Event)、事件跟踪提供者(Provider)和事件跟踪数据仓库(Repo)这三部分组成

下面我们分别看一下它们的实现。

所有的事件类型都遵循了一个名叫TrackingEventType的空协议(Protocol),其定义如下:

swift
protocol TrackingEventType { }

然后就可以定义该协议的字类型。根据用途的不同,我们把事件分成以下三类:

  • ScreenviewsTrackingEvent,用于记录页面事件;

  • ActionTrackingEventType,用于记录行为事件,例如点击了某个按钮;

  • TrackingEvent,用于记录通用的事件。

接着我们定义了TrackingProvider协议来发送事件。该协议定义了三个方法来分别发送不同的事件,具体定义如下:

swift
protocol TrackingProvider {
    func trackScreenviews(_ event: TrackingEventType)
    func trackAction(_ event: TrackingEventType)
    func trackEvent(_ event: TrackingEventType)
}

其中,trackScreenviews()用于发送页面事件,trackAction()负责发送行为事件,而trackEvent()用于发送通用的事件。

当要支持某种统计分析服务(例如 Firebase)的时候,我们就需要为TrackingProvider提供一个具体的实现类型,比如,下面就是FirebaseTrackingProvider的具体实现:

swift
struct FirebaseTrackingProvider: TrackingProvider {
    func trackScreenviews(_ event: TrackingEventType) {
        guard let event = event as? ScreenviewsTrackingEvent else {
            return
        }
        Analytics.logEvent(AnalyticsEventSelectContent, parameters: [
                            AnalyticsParameterScreenName: event.screenName,
                            AnalyticsParameterScreenClass: event.screenClass])
    }
    func trackAction(_ event: TrackingEventType) {
        guard let event = event as? FirebaseActionTrackingEvent else {
            return
        }
        Analytics.logEvent(AnalyticsEventSelectContent, parameters: event.parameters)
    }
    func trackEvent(_ event: TrackingEventType) {
        guard let event = event as? TrackingEvent else {
            return
        }
        Analytics.logEvent(event.name, parameters: event.parameters)
    }
}

FirebaseTrackingProvider遵循了TrackingProvider协议,并实现了trackScreenviews()trackAction()trackEvent()三个方法。在这些方法里面,都是通过guard语句来检查输入类型是否正确,并通过 Firebase SDK 所提供的Analytics.logEvent()方法来发送事件。

假如我们需要支持新的统计分析服务时,就可以为该服务提供一个遵循TrackingProvider协议的实现类型。例如,当我们支持 Mixpanel 时,就可实现一个名叫MixpanelTrackingProvider的结构体。

有了TrackingProvider的实例以后,我们就可以通过 Repo 来管理它们。这里我们一起看一下TrackingRepoType协议和它的实现类型TrackingRepo

TrackingRepoType的实现如下:

swift
protocol TrackingRepoType {
    func register(trackingProvider: TrackingProvider)
    func trackScreenviews(_ event: TrackingEventType)
    func trackAction(_ event: TrackingEventType)
    func trackEvent(_ event: TrackingEventType)
}

该协议定义了register(trackingProvider:)方法来注册各种TrackingProvider,然后还定义了三个方法来发送不同类型的事件。

TrackingRepo的具体实现如下:

swift
final class TrackingRepo: TrackingRepoType {
    static let shared: TrackingRepo = .init()
    private var providers = [TrackingProvider]()
    private init() { }
    func register(trackingProvider: TrackingProvider) {
        providers.append(trackingProvider)
    }
    func trackScreenviews(_ event: TrackingEventType) {
        providers.forEach { $0.trackScreenviews(event) }
    }
    func trackAction(_ event: TrackingEventType) {
        providers.forEach { $0.trackAction(event) }
    }
    func trackEvent(_ event: TrackingEventType) {
        providers.forEach { $0.trackEvent(event) }
    }
}

TrackingRepo实现了TrackingRepoType协议的所有方法,其中register(trackingProvider:)方法把各个注册的TrackingProvider的实例都保存在providers属性里面,而trackScreenviews()trackAction()trackEvent()三个方法则分别调用了providers属性所对应的方法。你可以看到,假如我们注册了多个统计分析服务的 Provider,TrackingRepo会把每个事件依次发送给各个后台服务。

到此为止,我们就实现了一套灵活的统计分析模块。有了它,我们可以很便捷地添加或者替换不同的统计分析服务。

2. 统计分析模块的使用

要使用统计分析模块,需要两步,第一步是注册TrackingProvider的实例,代码如下:

swift
[FirebaseTrackingProvider()].forEach {
    TrackingRepo.shared.register(trackingProvider: $0)
}

我们通过调用TrackingReporegister方法来注册FirebaseTrackingProvider的实例,这样就能把事件发送到 Firebase 统计分析服务了。如果有需要,我们还可以同时注册多个统计分析服务的 Provider。

第二步是使用TrackingRepo的实例来发送事件。例如,下面的代码演示了如何发送页面事件:

swift
trackingRepo.trackScreenviews(ScreenviewsTrackingEvent(screenName: L10n.Tracking.momentsScreen, screenClass: String(describing: self)))

我们通过调用trackingRepotrackScreenviews()方法来发送进入朋友圈页面的事件。

3. 为不同统计分析服务自定义事件内容

不同的统计分析服务所接收的事件内容可能不一样。这里我们以点赞按钮事件为例子看看如何为 Firebase 服务自定义事件。

首先我们看一下LikeActionTrackingEvent的定义:

swift
struct LikeActionTrackingEvent: ActionTrackingEventType {
    let momentID: String
    let userID: String
}

该事件只有两个属性,其中momentID表示点赞的朋友圈信息的 ID,而userID表示点赞用户的 ID。为了特定给 Firebase 统计分析服务定制事件的内容,我们定义了一个名叫FirebaseActionTrackingEvent的空协议:

swift
protocol FirebaseActionTrackingEvent: ActionTrackingEventType { }

然后给LikeActionTrackingEvent提供了一个类型扩展,具体代码如下:

swift
extension LikeActionTrackingEvent: FirebaseActionTrackingEvent {
    var parameters: [String : Any] {
        return [
            AnalyticsParameterItemID: "moment-id-\(momentID)-user-id-\(userID)",
            AnalyticsParameterItemName: "moment-like"
        ]
    }
}

该类型扩展使得LikeActionTrackingEvent遵循了FirebaseActionTrackingEvent协议,并为parameters属性提供了一个默认的实现,在实现里面使用了在 Firebase SDK 里面定义的两个常量:AnalyticsParameterItemIDAnalyticsParameterItemName。当我们使用了这些常量时,Firebase 统计分析后台就会把事件自动映射成选择内容的 Item ID 和名字。

那提供这样一个类型扩展到底有什么好处呢?我们再看一下FirebaseTrackingProvidertrackAction方法的实现。

swift
func trackAction(_ event: TrackingEventType) {
    guard let event = event as? FirebaseActionTrackingEvent else {
        return
    }
    Analytics.logEvent(AnalyticsEventSelectContent, parameters: event.parameters)
}

在该方法里面,我们通过guard语句检查传递进来的事件是否为FirebaseActionTrackingEvent类型,如果不是,程序就直接退出了。如果是,就调用event.parameters属性来获取事件的内容,这时候就会调用类型扩展里parameters属性的默认实现,类型扩展方法能保证FirebaseTrackingProvider只发送遵循了FirebaseActionTrackingEvent协议的事件类型。

假如我们需要为其他统计分析服务自定义事件的内容时,该怎么做呢?例如,为 Mixpanel 自定义事件,可以通过下面的代码实现:

swift
extension LikeActionTrackingEvent: MixpanelActionTrackingEvent {
    var parameters: [String : Any] {
        return [
            MixPanelEventKey: "moment-id-\(momentID)-user-id-\(userID)",
            MixPanelEventName: "moment-like"
        ]
    }
}

通过类型扩展来遵循不同的事件协议,我们就可以很灵活地为各个统计分析服务发送不同内容的事件了。

最后,我们再看一下如何发送LikeActionTrackingEvent事件,具体实现如下:

swift
trackingRepo.trackAction(LikeActionTrackingEvent(momentID: momentID, userID: userID))

trackingRepo会自动把事件发送到各个注册的统计分析服务中,并且根据LikeActionTrackingEvent的类型扩展来准备不同的事件内容。

4. Firebase 统计分析报告

完成了上述的开发工作后,我们就能收集用户行为数据了,并且还可以在 Firebase 的统计分析服务上查看相关的报告。下图是 Moments App 的统计分析报告:

我们可以在 Analyics 菜单下看到各种各样的报告,如事件统计、设备类型以及用户留存率等。这些报告能协助我们更准确地做出产品决定,比如,通过 iOS 活跃版本的报告能帮我们决定 App 支持 iOS 的最低版本号,假如绝大部分用户都使用 iOS 13 以上的版本,我们就引入 SwiftUI 和 Combine 等新技术。

我建议你仔细阅读统计分析服务的相关文档,并熟悉各种统计报告以及指标,从而助力产品的增长。

总结

在这一讲,我们讲述了如何架构一个灵活的统计分析模块,有了这个模块,我们就可以很方便地支持和替换不同的统计分析服务。同时,我们还以 Firebase 为例子讲述了如何配置 Firebase 的统计分析服务。总之,我希望你能好好地利用这些分析报告和指标,进而助力产品的增长。

思考题

请参照 FirebaseTrackingProvider 来编写一个遵循 TrackingProvider 协议的 SystemLogTrackingProvider 来打印日志,在该 Provider 里通过系统提供的 os_log() 方法来打印事件。

请把你的答案写到留言区或者提交一个 PR 哦。下一讲我将介绍如何使用 Firebase 的崩溃报告服务去解决线上的 Bug,记得按时来听课。

源码地址

统计分析模块源码地址:https://github.com/lagoueduCol/iOS-linyongjian/tree/main/Moments/Moments/Foundations/Analytics