SwiftUI App中接入友盟统计U-APP和性能监控U-APM

SwiftUI App中接入友盟统计U-APP和性能监控U-APM

目录

展开

App接入数据统计的意义

在App中接入数据统计,能使得开发者和产品经理能够深入了解用户行为、优化产品体验,并基于数据做出明智的决策。通过收集和分析用户数据,不仅可以识别潜在的问题和改进机会,还能实现个性化服务,从而提升用户满意度和留存率。

App中接入数据统计的方式

根据使用场景,目前一般有三种在App中接入数据统计的方式:

  1. 使用第三方统计。如果是海外应用,可以考虑使用Firebase全家桶中的Google Analytics, 或者Flurry。国内的话,则可以考虑使用的友盟
  2. 自己开发实现。如果不想依赖第三方平台,受第三方平台的限制,或者对于数据隐私有较高要求,可以自己在App里实现向数据上报的逻辑,把数据上报到服务端,由服务端收集后处理分析用户数据并展示(如使用Grafana)
  3. 使用开源项目。 基于一些开源的项目自己搭建数据统计平台,如posthog, aptabase

在SwiftUI中接入友盟统计

本文主要介绍在SwiftUI中,如何接入友盟统计U-APP以及性能监控U-APM。友盟的官方文档中,都是基于Objective-C的示例代码。或者使用Swift语言在UIKIt框架下的接入方式。而没有结合SwiftUI框架的使用示例,因此也有了这篇文章,记录下自己在SwiftUI中接入友盟统计时的步骤以及所踩的坑。废话不多说,直接开干。整个过程主要分3步:

  1. 在友盟中创建应用
  2. 在App中集成友盟的SDK
  3. 集成测试

在友盟创建应用

注册一个友盟账号(如果没有的话),登录后,打开链接创建一个应用, 应用类型选择App端,然后选择新建

根据实际情况,填入App的相关信息,如下图所示,最后点击注册应用

接下来,我们会看到两个比较重要的信息: 第一个是红色圈出来的AppKey, 这个信息我们需要注意,后面接入SDK时会在代码中使用(AppKey在创建好应用之后,随时都可以在App的信息中查看到,不需要特意记录下来)。

第二个是选择要开始使用的服务。友盟除了数据统计,应用性能监控之外,还有消息推送,智能认证等,我们可以根据自身的需求选择开通对应的服务,这里我选择了开通移动统计U-App以及应用性能监控U-APM服务。(这里也不用特别纠结需要选择哪些服务,因为应用创建好之后,我们可以随时修改需要开通的服务)

选择好之后,点击确认开通。记录下我们的App Key, App的创建也就完成了。

在App中集成友盟的SDK

在友盟中创建好应用后,接下来就可以在App中集成SDK了。这里我们以创建一个名为HelloUMeng的项目为例。

使用Xcode新建项目

打开Xcode,创建一个名为HelloUMeng的项目,这个是常规操作,不再赘述。

Info

本文章中后面所有使用到的项目源码,如果有需要的话,可以从Github下载

Cocoapods安装

官方推荐使用cocoapods来安装友盟的SDK, 如果App已经配置好了Cocoapods,则可以跳过这一步。没有的话,可以在项目的根目录下使用,先创建一个Gemfile文件

# frozen_string_literal: true

source "https://rubygems.org"

gem "cocoapods"

再创建如下脚本scripts/setup.sh,并在项目根目录下执行,可以一键配置好rbenv虚拟环境,并使用bundler安装好Cocoapods

#!/bin/sh

# Install homebrew
which brew || curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | bash

# Install rbenv
which rbenv || curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash

# Install ruby using rbenv
latest_stable_ruby_version=$(rbenv install -l | grep -v - | tail -1)
echo $latest_stable_ruby_version > .ruby-version
if [[ ! -d "$HOME/.rbenv/versions/$ruby_version" ]]; then
  rbenv install $ruby_version;
  rbenv init
fi

# Install bunlder with specical version
gem install bundler

# Install all gems
bundle install

创建Podfile

引入需要使用的Pods, 如下所示:

# Uncomment the next line to define a global platform for your project
platform :ios, '17.0'

def umeng_pods
  # 基础库,必须集成
  pod 'UMCommon'
  pod 'UMDevice'

  # 开发阶段进行调试SDK及相关功能使用,可在发布 App 前移除
  pod 'UMCCommonLog'

  # 下面的库根据需要使用的服务选择性按照
  pod 'UMAPM' # APM组件,原错误分析升级为独立产品U-APM
  #pod 'UMPush'	# 推送组件,由原来的UMCPush变为UMPush
  #pod 'UMShare/UI' 	# 可选,UI模块(分享面板),由原来的UMCShare/UI变为了UMShare/UI

  # 分享SDK 在线依赖其它平台仅支持手动集成[友盟+官网-开发者中心-sdk下载页-sdk下载]
  #pod 'UMShare/Social/WeChat'
  #pod 'UMShare/Social/Sina'
  #pod 'UMShare/Social/QQ'
  #pod 'UMLink'   			# 智能超链组件
  #pod 'UMVerify'			# 智能认证组件
end

target 'HelloUMeng' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for TimeMastery
  umeng_pods
end

然后执行pod install安装SDK,如果使用的是iOS 18和Xcode 16,pod install命令会失败,并给出如下错误提示

RuntimeError - `PBXGroup` attempted to initialize an object with unknown ISA `PBXFileSystemSynchronizedRootGroup` from attributes: `{"isa"=>"PBXFileSystemSynchronizedRootGroup", "path"=>"HelloUMeng", "sourceTree"=>"<group>"}`

参考下面的文章可以解决这个问题:

CocoaPods pod init Fails with “Unknown ISA PBXFileSystemSynchronizedRootGroup” Error on Xcode 16

具体步骤如下:

  1. 关闭Xcode
  2. 在项目根目录下,删除Podfile.lock文件和Pods目录
  3. 编辑HelloUMeng.xcodeproj/project.pbxproj文件,将里面的PBXFileSystemSynchronizedRootGroup全部替换为PBXGroup
  4. 编辑HelloUMeng.xcodeproj/project.pbxproj文件, 找到objectVersion, 将它修改为objectVersion = 56 修改后再次执行pod install, 这样就可以安装成功了

创建桥接头文件

由于友盟的SDK都是使用Object-C编写的,要想通过Swift语言调用它们,必须创建桥接头文件.

使用Xcode打开项目,这里需要注意: 使用Cocoapods管理项目后,需要通过open HelloUMeng.xcworkspace/命令打开项目。然后新建桥接头文件,在Xcode中,首先选中HelloUmeng这Target的目录,右键点击,选择New File from templates

输入文件名为HelloUMeng-Bridging-Header.h, 并且不需要关联选择任何Targets

创建好文件后,编辑文件输入如下内容

#ifndef UMengBridging_Header_h

//导入UMCommon的OC的头文件
#import <UMCommon/UMCommon.h>
//导入UMAPM的OC的头文件
#import <UMAPM/UMLaunch.h>
#import <UMAPM/UMCrashConfigure.h>
#import <UMAPM/UMAPMConfig.h>

//导入UMCommonLog的OC的头文件(如需加入日志库 把此注释打开)
#import <UMCommonLog/UMCommonLogManager.h>

//导入UMCommon的OC的头文件
#import <UMCommon/UMConfigure.h>

//导入UMAnalytics的OC的头文件
#import <UMCommon/MobClick.h>


//导入UMAnalytics Game的OC的头文件(如需游戏统计,可选)
//#import <UMAnalyticsGame/MobClickGameAnalytics.h>

#endif /* UMengBridging_Header_h */

配置桥接头文件

选中项目,在Build Settings中,找到Objective-C Bridging Header,设置为刚刚创建的桥接头文件的路径,并设置Precompile Bridging HeaderYes

配置好之后,运行App, 看是否会报错,如果没有报错,则说明桥接头文件配置成功。下面列出了作者在使用时遇到的一些错误,也附上了解决方法,希望可以帮助大家少踩坑。

错误解决
错误一 Building for ‘iOS-simulator’, but linking in object file

如果编译项目时遇到如下错误

Building for 'iOS-simulator', but linking in object file ( .../HelloUMeng/Pods/UMCCommonLog/UMCommonLog/UMCommonLog.framework/UMCommonLog[arm64][2](UMCommonLog.o)) built for 'iOS'

说明运行App使用的模拟器和pod库的不兼容导致的。切换到真机调试就不会有问题。如果还是想用模拟器,可以按下面的方式修改 需要调整 Build settings中的Excluded Architecture,添加上arm64

同时修改Podfile文件,添加如下配置,避免pod install时覆盖了上面的项目配置

# 修复报错
# Xcode building for iOS Simulator, but linking in an object file built for iOS, for architecture 'arm64'
# https://stackoverflow.com/questions/63607158/xcode-building-for-ios-simulator-but-linking-in-an-object-file-built-for-ios-f
post_install do |installer|
  installer.pods_project.build_configurations.each do |config|
    config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
  end
end

注意

使用上面的设置后,当在真机上运行App时,又会报错

Build input file cannot be found: '/Users/.../Library/Developer/Xcode/DerivedData/HelloUMeng-andwdcqdjmobxjawstdkqvnkofgt/Build/Products/Debug-iphoneos/HelloUMeng.app/HelloUMeng.debug.dylib'. Did you forget to declare this file as an output of a script phase or custom build rule which produces it?

这时只需要在Build settings中的Excluded Architecture,去掉arm64就没有问题了。

错误二 Sandbox: bash(51846) deny(1) file-write-create

如果编译项目时遇到如下错误

Sandbox: bash(51846) deny(1) file-write-create ../HelloUMeng/Pods/resources-to-copy-HelloUMeng.txt

找到 Build settings中的User Script Sandboxing, 将它设置为false

初始化SDK

下载示例代码
  1. 参考友盟官方的Swift Demo示例, 下载示例代码后,我们可以自己看一看,了解下SDK的基本使用。解压后,将其中UMAnalyticsSwiftDemo/UMSwift目录下的UMAnalyticsSwift.swift, UMCommonLogSwift.swiftUMCommonSwift.swift三个文件拷贝到当前项目中,这里我并没有拷贝UMGameAnalyticsSwift.swift文件,因为里面是封装的是游戏统计相关的操作。我暂时不需要,拷贝后的效果如下:
创建AppDelegate.swift文件

在项目中,新建一个AppDelegate.swift文件, 设置如下内容:

import UIKit


class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject {
    func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
        // 根据实际情况替换
        initUmeng(appKey: "6718d7bd80464b33f6e30afe")
        return true
    }

    func initUmeng(appKey: String) {
        #if DEBUG
            UMCommonLogSwift.setUpUMCommonLogManager()
            UMCommonSwift.setLogEnabled(bFlag: true)
        #endif
        // Channel为渠道号,为空时表示App Store
        UMCommonSwift.initWithAppkey(appKey: appKey, channel: "")
    }
}

上面的appKey就是第一步中在友盟中创建了App之后生成的appKey,如果忘记了,可以在应用信息中查看

修改HelloUMengApp.swift文件

使用刚刚创建的AppDelegate

import SwiftUI

@main
struct HelloUMengApp: App {
     // 设置使用AppDelegate
    @UIApplicationDelegateAdaptor private var appDelegate: AppDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
验证

配置成功后,启动项目,在控制台可以看到类型如下输出

集成测试

根据友盟官方文档的描述

集成测试是通过收集和展示已注册测试设备发送的日志,来检验SDK集成有效性和完整性的一个服务。 所有由注册设备发送的应用日志将实时地进行展示,您可以方便地查看包括应用版本、渠道名称、自定义事件、页面访问情况等数据,提升集成与调试的工作效率。

简单来说,集成测试就是让我们可以实时看到设备使用SDK的情况,以确保我们的集成的友盟SDK按照我们预期的想法在上报统计数据和异常信息。

要使用集成测试,需要完成以下步骤

配置 URL Schema

在项目设置 target -> 选项卡 Info - > URL Types,填入的URL Schemes,格式为um.appKey,如: um.6718d7bd80464b33f6e30afe6718d7bd80464b33f6e30afe就是我们第一步中创建的友盟App Key

配置接受URL

这里需要注意,官方文档给的示例是在AppDelegate.swift文件中,添加下面的方法来配置接受URL的

func application(_: UIApplication, open url: URL, options _: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
	print("open url in AppDelegate: \(url.absoluteString)")
	if MobClick.handle(url) {
		print("handle url: \(url.absoluteString)")
		return true
	}
	// 其它第三方处理
	return true
}

但实际测试时发现,在SwiftUI中,上述方法并不会调用,而是通过onOpenURL修饰符来代替

import SwiftUI

@main
struct HelloUMengApp: App {

    // 设置使用AppDelegate
    @UIApplicationDelegateAdaptor private var appDelegate: AppDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
            .onOpenURL { url in
                // 处理友盟的集成测试调用
                print("onOpenURL: \(url)")
                MobClick.handle(url)
            }
        }
    }
}

如果这里配置错误,会导致集成测试时无法看到收集的数据

开启实时监控

在登陆了友盟账户的情况下,打开实时日志的页面。选择待验证应用为我们刚刚创建的HelloUMeng,如下图所示:

首先确保使用最新的代码编译并将App安装在手机上,然后使用手机系统自带的照相App扫描上面的二维码。会打开如下网页

点击”开始测试”按钮,会唤起我们的App. 这时在网页就可以看到SDK自动上报”App启动”的事件信息

说明我们的SDK配置正常,且没有任何问题。

自定义上报事件和App崩溃日志

我们也可以自定义上报的事件,修改ContentView.swift的代码,添加两个按钮,分别用于触发上报自定义事件,和引起App崩溃

struct ContentView: View {
    var body: some View {
        VStack {
            Button("上报自定义事件") {
                UMAnalyticsSwift.event(eventId: "eventA")
            }
            .buttonStyle(.borderedProminent)

            Button("App崩溃上报") {
                fatalError("App崩溃上报测试")
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
    }
}

修改代码后,把App安装到手机上。

测试自定义上报事件

再次重复前面的步骤,打开实时日志的页面,扫描二维码唤起App,然后点击App中的上报自定义事件的按钮,则可以在页面看到上报的事件信息。

说明统计功能完全没有问题。

测试App崩溃日志上报

崩溃日志的查看和统计数据不在同一个地方,打开友盟的U-APM,在”应用列表中”,找到我们创建的”HelloUMeng” App, 选择”集成测试“

在打开的页面选择”开始测试”,会看到类似统计上报事件时相同的页面

同样扫描二维码,在打开的网页中,点击”关联应用开始集成测试”的按钮

唤起App后,点击App中的”App崩溃上报”按钮,引起App崩溃,再次打开App,让SDK上报事件(因为崩溃类的日志上报时机是在App崩溃后,下一次打开App时才上报,关于不同类似事件的上报时机,可以参考友盟的官方文档)

在网页上,正常情况下应该是能像数据统计上报一样,实时看到上报的日志,但是我多次测试,并且让App崩溃多次,都没有能看到实时日志。

反而是等待几分钟后,在友盟的U-APM,的”应用列表中”,看到了捕获的App异常情况。集成状态也由开始的”未集成”变成了”已集成”状态。 点击应用名,在实时概览中,可以看到App采集的异常日志情况

在点击”查看详情”,可以看到异常日志的采集情况。

注意,在测试异常上报时,不要使用自定义异常来测试,因为自定义异常上传的功能免费版本的U-APM并不支持。

// APM免费版本不支持
Button("自定义异常上报") {
	do {
		throw CustomError.NetworkError
	} catch {
		print("上报自定义错误测试")
		UMCrashConfigure.reportException(withName: "崩溃采集测试", reason: "测试", stackTrace: [error])
	}
}

至此,整个测试流程完成,说明我们数据统计U-APP和U-APM的SDK集成都没有问题。当然,这里也只是简单使用了SDK,更多关于数据统计和APM采集相关的使用,可以参考友盟的官方文档。

再次强调下

Info

本文章中使用的项目源码,如果有需要的话,可以从Github下载