
网罗开发 (小红书、快手、视频号同名)
大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验 。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索"展菲",即可纵览我在各大平台的知识足迹。
📣 公众号"Swift社区",每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友"fzhanfei",与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
-
- 前言
- [Xcode Organizer 的局限性](#Xcode Organizer 的局限性)
-
- [Organizer 提供的信息](#Organizer 提供的信息)
- [Organizer 的不足之处](#Organizer 的不足之处)
- [MetricKit 框架介绍](#MetricKit 框架介绍)
-
- [MetricKit 的核心组件](#MetricKit 的核心组件)
- [如何集成 MetricKit](#如何集成 MetricKit)
-
- 第一步:定义分析接口
- 第二步:实现分析服务
- [第三步:在 AppDelegate 中集成 MetricKit](#第三步:在 AppDelegate 中集成 MetricKit)
- 代码详细分析
- 实际应用场景
- 注意事项和最佳实践
- 总结
- [完整可运行 Demo 代码](#完整可运行 Demo 代码)
前言
最近在做一个 iOS 项目的时候,遇到了一个让人头疼的问题:应用在用户设备上经常被系统终止,但我们在 Xcode Organizer 里看到的崩溃报告信息不够详细,很难定位问题。Xcode Organizer 确实提供了很多有用的性能指标,比如崩溃、能耗影响、卡顿、启动时间、内存消耗和应用终止等,但对于某些问题,特别是应用终止的原因,它提供的信息还不够充分。
为了解决这个问题,Apple 推出了 MetricKit 框架,让我们可以收集更详细的诊断信息,并构建自己的性能监控面板。今天我们就来聊聊如何使用 MetricKit 来监控应用性能,以及如何在实际项目中应用它。
Xcode Organizer 的局限性
Xcode Organizer 确实是一个很好的工具,它提供了很多有用的性能指标。但有时候,我们需要更详细的信息来解决问题。
Organizer 提供的信息
Xcode Organizer 可以显示:
- 崩溃报告:应用崩溃时的堆栈信息
- 能耗影响:应用对设备电池的影响
- 卡顿:应用主线程卡顿的情况
- 启动时间:应用启动所需的时间
- 内存消耗:应用使用的内存情况
- 应用终止:应用被系统终止的次数
这些信息确实很有用,但对于某些问题,特别是应用终止的原因,信息还不够详细。
Organizer 的不足之处
比如,当应用被系统终止时,Organizer 可能只会告诉你"应用被终止了",但不会告诉你:
- 是因为内存不足被终止的吗?
- 是因为 CPU 资源限制被终止的吗?
- 是因为内存压力被终止的吗?
- 是因为 OOM(Out of Memory)被终止的吗?
这些信息对于定位问题非常重要,但 Organizer 提供的信息不够详细。
MetricKit 框架介绍
MetricKit 是 Apple 在 iOS 13 中引入的一个框架,它让我们可以收集更详细的性能指标和诊断信息。通过 MetricKit,我们可以:
- 收集详细的性能指标:包括启动时间、内存使用、CPU 使用等
- 收集诊断信息:包括崩溃报告、CPU 异常、磁盘写入异常等
- 构建自定义的性能监控面板:将收集到的数据上传到自己的服务器,进行分析和展示
MetricKit 的核心组件
MetricKit 主要有以下几个核心组件:
- MXMetricManager:管理性能指标的收集和分发
- MXMetricPayload:包含性能指标数据
- MXDiagnosticPayload:包含诊断信息数据
- MXMetricManagerSubscriber:订阅性能指标和诊断信息的协议
如何集成 MetricKit
集成 MetricKit 其实挺简单的,主要分为几个步骤:
第一步:定义分析接口
首先,我们需要定义一个分析接口,用于记录性能指标和崩溃信息:
swift
protocol Analytics {
func logEvent(_ name: String, value: String)
func logCrash(_ crash: MXCrashDiagnostic)
}
这个接口定义了两个方法:
logEvent:记录性能事件,比如应用终止次数、CPU 退出次数等logCrash:记录崩溃信息,包括崩溃堆栈、异常类型等
第二步:实现分析服务
接下来,我们需要实现这个接口。在实际项目中,你可以集成 Firebase Analytics、Mixpanel、或者自己的后端服务:
swift
class FirebaseAnalytics: Analytics {
func logEvent(_ name: String, value: String) {
// 使用 Firebase Analytics 记录事件
Analytics.logEvent(name, parameters: ["value": value])
}
func logCrash(_ crash: MXCrashDiagnostic) {
// 记录崩溃信息
let crashInfo: [String: Any] = [
"exception_type": crash.exceptionType.rawValue,
"exception_code": crash.exceptionCode,
"signal": crash.signal,
"termination_reason": crash.terminationReason ?? "unknown"
]
Analytics.logEvent("crash", parameters: crashInfo)
}
}
第三步:在 AppDelegate 中集成 MetricKit
现在,我们需要在 AppDelegate 中集成 MetricKit:
swift
import MetricKit
final class AppDelegate: NSObject, UIApplicationDelegate, MXMetricManagerSubscriber {
private var analytics: Analytics?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// 初始化分析服务
analytics = FirebaseAnalytics()
// 订阅 MetricKit
MXMetricManager.shared.add(self)
return true
}
// 接收性能指标数据
nonisolated func didReceive(_ payloads: [MXMetricPayload]) {
for payload in payloads {
// 处理应用退出指标
if let exitMetrics = payload.applicationExitMetrics?.backgroundExitData {
analytics?.logEvent(
"performance_abnormal_exit",
value: exitMetrics.cumulativeAbnormalExitCount.formatted()
)
analytics?.logEvent(
"performance_cpu_exit",
value: exitMetrics.cumulativeCPUResourceLimitExitCount.formatted()
)
analytics?.logEvent(
"performance_memory_exit",
value: exitMetrics.cumulativeMemoryPressureExitCount.formatted()
)
analytics?.logEvent(
"performance_oom_exit",
value: exitMetrics.cumulativeMemoryResourceLimitExitCount.formatted()
)
}
}
}
// 接收诊断信息数据
nonisolated func didReceive(_ payloads: [MXDiagnosticPayload]) {
for payload in payloads {
if let crashes = payload.crashDiagnostics {
for crash in crashes {
analytics?.logCrash(crash)
}
}
}
}
}
代码详细分析
让我们详细分析一下这段代码的每个部分:
MXMetricManager 的使用
swift
MXMetricManager.shared.add(self)
这行代码的作用是订阅 MetricKit 的性能指标和诊断信息。MXMetricManager 是一个单例,我们通过调用 add(self) 方法来添加一个订阅者。当系统收集到性能指标或诊断信息时,会调用订阅者的相应方法。
MXMetricManagerSubscriber 协议
MXMetricManagerSubscriber 协议定义了两个可选方法:
didReceive(_ payloads: [MXMetricPayload]):当系统收集到性能指标时调用didReceive(_ payloads: [MXDiagnosticPayload]):当系统收集到诊断信息时调用
这两个方法都标记为 nonisolated,这意味着它们可以在任何线程上调用,不需要在主线程上执行。
MXMetricPayload 详解
MXMetricPayload 包含了各种性能指标数据,它继承自 MXMetric 抽象类。主要包含以下属性:
-
applicationLaunchMetrics:应用启动相关的指标
- 启动时间
- 启动时的内存使用
- 启动时的 CPU 使用
-
applicationExitMetrics:应用退出相关的指标
- backgroundExitData :后台退出数据
cumulativeAbnormalExitCount:累计异常退出次数cumulativeCPUResourceLimitExitCount:累计 CPU 资源限制退出次数cumulativeMemoryPressureExitCount:累计内存压力退出次数cumulativeMemoryResourceLimitExitCount:累计内存资源限制退出次数(OOM)
- foregroundExitData:前台退出数据(类似后台退出数据)
- backgroundExitData :后台退出数据
-
applicationTimeMetrics:应用运行时间相关的指标
- 前台运行时间
- 后台运行时间
-
applicationResponsivenessMetrics:应用响应性相关的指标
- 卡顿次数
- 卡顿持续时间
-
memoryMetrics:内存使用相关的指标
- 峰值内存使用
- 平均内存使用
-
cpuMetrics:CPU 使用相关的指标
- 峰值 CPU 使用
- 平均 CPU 使用
-
diskIOMetrics:磁盘 IO 相关的指标
- 读取次数
- 写入次数
- 读取数据量
- 写入数据量
-
networkTransferMetrics:网络传输相关的指标
- 上传数据量
- 下载数据量
MXDiagnosticPayload 详解
MXDiagnosticPayload 包含了各种诊断信息数据,它继承自 MXDiagnostic 抽象类。主要包含以下属性:
-
crashDiagnostics:崩溃诊断信息
exceptionType:异常类型exceptionCode:异常代码signal:信号terminationReason:终止原因virtualMemoryRegionInfo:虚拟内存区域信息callStackTree:调用堆栈树
-
cpuExceptionDiagnostics:CPU 异常诊断信息
- CPU 异常类型
- CPU 异常代码
- 调用堆栈信息
-
diskWriteExceptionDiagnostics:磁盘写入异常诊断信息
- 磁盘写入异常类型
- 磁盘写入异常代码
- 调用堆栈信息
-
hangDiagnostics:卡顿诊断信息
- 卡顿持续时间
- 调用堆栈信息
处理应用退出指标
在我们的代码中,我们主要关注应用退出指标:
swift
if let exitMetrics = payload.applicationExitMetrics?.backgroundExitData {
analytics?.logEvent(
"performance_abnormal_exit",
value: exitMetrics.cumulativeAbnormalExitCount.formatted()
)
analytics?.logEvent(
"performance_cpu_exit",
value: exitMetrics.cumulativeCPUResourceLimitExitCount.formatted()
)
analytics?.logEvent(
"performance_memory_exit",
value: exitMetrics.cumulativeMemoryPressureExitCount.formatted()
)
analytics?.logEvent(
"performance_oom_exit",
value: exitMetrics.cumulativeMemoryResourceLimitExitCount.formatted()
)
}
这段代码的作用是:
- 检查是否有后台退出数据
- 记录异常退出次数
- 记录 CPU 资源限制退出次数
- 记录内存压力退出次数
- 记录内存资源限制退出次数(OOM)
这些信息可以帮助我们了解应用被系统终止的原因。
处理崩溃诊断信息
swift
if let crashes = payload.crashDiagnostics {
for crash in crashes {
analytics?.logCrash(crash)
}
}
这段代码的作用是:
- 检查是否有崩溃诊断信息
- 遍历所有崩溃信息
- 记录每个崩溃的详细信息
崩溃信息包括异常类型、异常代码、信号、终止原因和调用堆栈等,这些信息对于定位崩溃原因非常重要。
时间戳信息
MXMetricPayload 和 MXDiagnosticPayload 都提供了 timeStampBegin 和 timeStampEnd 属性,让我们可以知道每个数据包覆盖的时间范围:
swift
let timeRange = payload.timeStampBegin...payload.timeStampEnd
print("数据时间范围: \(timeRange)")
这对于分析性能趋势非常有用。
实际应用场景
MetricKit 在实际项目中的应用场景非常广泛,让我们看看几个常见的应用场景:
场景一:监控应用终止原因
应用被系统终止是一个常见的问题,但很难定位原因。通过 MetricKit,我们可以详细记录应用终止的原因:
swift
nonisolated func didReceive(_ payloads: [MXMetricPayload]) {
for payload in payloads {
if let exitMetrics = payload.applicationExitMetrics {
// 后台退出数据
if let backgroundExit = exitMetrics.backgroundExitData {
let abnormalExits = backgroundExit.cumulativeAbnormalExitCount
let cpuExits = backgroundExit.cumulativeCPUResourceLimitExitCount
let memoryExits = backgroundExit.cumulativeMemoryPressureExitCount
let oomExits = backgroundExit.cumulativeMemoryResourceLimitExitCount
// 记录到分析服务
analytics?.logEvent("background_abnormal_exits", value: "\(abnormalExits)")
analytics?.logEvent("background_cpu_exits", value: "\(cpuExits)")
analytics?.logEvent("background_memory_exits", value: "\(memoryExits)")
analytics?.logEvent("background_oom_exits", value: "\(oomExits)")
}
// 前台退出数据
if let foregroundExit = exitMetrics.foregroundExitData {
let abnormalExits = foregroundExit.cumulativeAbnormalExitCount
let cpuExits = foregroundExit.cumulativeCPUResourceLimitExitCount
let memoryExits = foregroundExit.cumulativeMemoryPressureExitCount
let oomExits = foregroundExit.cumulativeMemoryResourceLimitExitCount
// 记录到分析服务
analytics?.logEvent("foreground_abnormal_exits", value: "\(abnormalExits)")
analytics?.logEvent("foreground_cpu_exits", value: "\(cpuExits)")
analytics?.logEvent("foreground_memory_exits", value: "\(memoryExits)")
analytics?.logEvent("foreground_oom_exits", value: "\(oomExits)")
}
}
}
}
这样,我们就可以在分析平台上看到应用终止的详细原因,比如有多少次是因为内存不足被终止的,有多少次是因为 CPU 资源限制被终止的。
场景二:监控应用启动性能
应用启动时间是用户体验的重要指标,通过 MetricKit,我们可以监控应用启动性能:
swift
nonisolated func didReceive(_ payloads: [MXMetricPayload]) {
for payload in payloads {
if let launchMetrics = payload.applicationLaunchMetrics {
// 启动时间(秒)
let launchTime = launchMetrics.timeToFirstDraw
analytics?.logEvent("app_launch_time", value: "\(launchTime)")
// 启动时的内存使用(MB)
if let memoryUsage = launchMetrics.memoryMetrics {
let peakMemory = memoryUsage.peakMemoryUsage.value
analytics?.logEvent("app_launch_peak_memory", value: "\(peakMemory)")
}
// 启动时的 CPU 使用
if let cpuUsage = launchMetrics.cpuMetrics {
let peakCPU = cpuUsage.cumulativeCPUTime.value
analytics?.logEvent("app_launch_cpu_time", value: "\(peakCPU)")
}
}
}
}
这样,我们就可以监控应用启动性能,并在启动时间过长时及时发现问题。
场景三:监控内存使用情况
内存使用是应用性能的重要指标,通过 MetricKit,我们可以监控内存使用情况:
swift
nonisolated func didReceive(_ payloads: [MXMetricPayload]) {
for payload in payloads {
if let memoryMetrics = payload.memoryMetrics {
// 峰值内存使用(MB)
let peakMemory = memoryMetrics.peakMemoryUsage.value
analytics?.logEvent("app_peak_memory", value: "\(peakMemory)")
// 平均内存使用(MB)
let averageMemory = memoryMetrics.averageMemoryUsage.value
analytics?.logEvent("app_average_memory", value: "\(averageMemory)")
// 如果峰值内存超过阈值,记录警告
if peakMemory > 500 { // 500MB
analytics?.logEvent("memory_warning", value: "\(peakMemory)")
}
}
}
}
这样,我们就可以监控内存使用情况,并在内存使用过高时及时发现问题。
场景四:监控应用卡顿情况
应用卡顿会严重影响用户体验,通过 MetricKit,我们可以监控应用卡顿情况:
swift
nonisolated func didReceive(_ payloads: [MXMetricPayload]) {
for payload in payloads {
if let responsivenessMetrics = payload.applicationResponsivenessMetrics {
// 卡顿次数
let hangCount = responsivenessMetrics.hangCount
analytics?.logEvent("app_hang_count", value: "\(hangCount)")
// 如果卡顿次数过多,记录警告
if hangCount > 10 {
analytics?.logEvent("app_hang_warning", value: "\(hangCount)")
}
}
}
}
这样,我们就可以监控应用卡顿情况,并在卡顿次数过多时及时发现问题。
场景五:构建自定义性能监控面板
通过 MetricKit,我们可以构建自己的性能监控面板,将收集到的数据上传到自己的服务器:
swift
nonisolated func didReceive(_ payloads: [MXMetricPayload]) {
for payload in payloads {
// 将数据转换为 JSON
if let jsonData = try? JSONEncoder().encode(payload.jsonRepresentation()) {
// 上传到服务器
uploadToServer(jsonData)
}
}
}
func uploadToServer(_ data: Data) {
var request = URLRequest(url: URL(string: "https://your-api.com/metrics")!)
request.httpMethod = "POST"
request.httpBody = data
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("上传失败: \(error)")
} else {
print("上传成功")
}
}.resume()
}
这样,我们就可以在自己的服务器上分析和展示性能数据,构建自己的性能监控面板。
注意事项和最佳实践
在使用 MetricKit 时,有几个注意事项和最佳实践:
数据收集的时机
MetricKit 不会立即发送数据,系统可能会聚合数据并按日发送。在极少数情况下,可能会更频繁地发送,但你不应该依赖任何特定的时间表。
这意味着:
- 数据可能会有延迟,不是实时的
- 数据是聚合的,不是每次事件都会发送
- 不要依赖特定的发送频率
数据量控制
MetricKit 收集的数据量可能很大,特别是崩溃诊断信息。在上传数据时,要注意:
- 压缩数据:在上传前压缩数据,减少网络传输量
- 批量上传:将多个数据包批量上传,减少网络请求次数
- 错误处理:处理上传失败的情况,可能需要重试机制
隐私和安全
MetricKit 收集的数据可能包含敏感信息,比如调用堆栈、内存内容等。在处理这些数据时,要注意:
- 数据加密:在上传数据时使用 HTTPS,确保数据传输安全
- 数据脱敏:在存储数据时,考虑对敏感信息进行脱敏处理
- 用户同意:确保用户同意收集这些数据,符合隐私政策
性能影响
MetricKit 的数据收集和处理可能会影响应用性能,要注意:
- 异步处理:在后台线程处理数据,不要阻塞主线程
- 延迟处理:如果数据量很大,可以考虑延迟处理,在应用空闲时处理
- 资源限制:注意内存和 CPU 使用,避免影响应用性能
总结
MetricKit 是一个强大的工具,它填补了 Xcode Organizer 的空白,让我们可以深入了解应用在真实环境中的表现。通过订阅 MXMetricManager 并处理 MXMetricPayload 和 MXDiagnosticPayload,我们可以获得应用启动、终止、崩溃和资源使用等方面的可见性,这些信息在其他情况下很难或不可能完全理解。
在实际项目中,MetricKit 可以帮助我们:
- 定位问题:通过详细的性能指标和诊断信息,快速定位问题
- 优化性能:通过监控性能指标,发现性能瓶颈并优化
- 提升用户体验:通过监控应用启动时间、卡顿等情况,提升用户体验
- 构建监控系统:构建自己的性能监控面板,更好地管理应用性能
虽然 MetricKit 有一些限制(比如数据收集的时机不确定),但它仍然是一个非常有用的工具。如果你正在开发一个需要监控性能的应用,MetricKit 绝对值得一试。
希望这篇文章能帮助你理解 MetricKit,并在实际项目中应用它!
完整可运行 Demo 代码
下面是一个完整的可运行示例,展示了如何集成和使用 MetricKit:
swift
import UIKit
import MetricKit
// 分析接口
protocol Analytics {
func logEvent(_ name: String, value: String)
func logCrash(_ crash: MXCrashDiagnostic)
}
// 简单的控制台分析实现(实际项目中可以替换为 Firebase、Mixpanel 等)
class ConsoleAnalytics: Analytics {
func logEvent(_ name: String, value: String) {
print("[Analytics] Event: \(name) = \(value)")
}
func logCrash(_ crash: MXCrashDiagnostic) {
print("[Analytics] Crash:")
print(" Exception Type: \(crash.exceptionType.rawValue)")
print(" Exception Code: \(crash.exceptionCode)")
print(" Signal: \(crash.signal)")
print(" Termination Reason: \(crash.terminationReason ?? "unknown")")
if let callStackTree = crash.callStackTree {
print(" Call Stack Tree: \(callStackTree)")
}
}
}
// AppDelegate
@main
class AppDelegate: UIResponder, UIApplicationDelegate, MXMetricManagerSubscriber {
var window: UIWindow?
private var analytics: Analytics?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// 初始化分析服务
analytics = ConsoleAnalytics()
// 订阅 MetricKit
MXMetricManager.shared.add(self)
print("MetricKit 已订阅")
// 创建窗口和视图控制器
window = UIWindow(frame: UIScreen.main.bounds)
let viewController = ViewController()
window?.rootViewController = viewController
window?.makeKeyAndVisible()
return true
}
// 接收性能指标数据
nonisolated func didReceive(_ payloads: [MXMetricPayload]) {
print("\n=== 收到性能指标数据 ===")
print("数据包数量: \(payloads.count)")
for (index, payload) in payloads.enumerated() {
print("\n--- 数据包 \(index + 1) ---")
print("时间范围: \(payload.timeStampBegin) 到 \(payload.timeStampEnd)")
// 应用启动指标
if let launchMetrics = payload.applicationLaunchMetrics {
print("应用启动指标:")
print(" 首次绘制时间: \(launchMetrics.timeToFirstDraw) 秒")
if let memoryMetrics = launchMetrics.memoryMetrics {
print(" 启动时峰值内存: \(memoryMetrics.peakMemoryUsage.value) MB")
}
if let cpuMetrics = launchMetrics.cpuMetrics {
print(" 启动时 CPU 时间: \(cpuMetrics.cumulativeCPUTime.value) 秒")
}
}
// 应用退出指标
if let exitMetrics = payload.applicationExitMetrics {
print("应用退出指标:")
// 后台退出数据
if let backgroundExit = exitMetrics.backgroundExitData {
print(" 后台退出:")
print(" 异常退出次数: \(backgroundExit.cumulativeAbnormalExitCount)")
print(" CPU 资源限制退出次数: \(backgroundExit.cumulativeCPUResourceLimitExitCount)")
print(" 内存压力退出次数: \(backgroundExit.cumulativeMemoryPressureExitCount)")
print(" 内存资源限制退出次数 (OOM): \(backgroundExit.cumulativeMemoryResourceLimitExitCount)")
// 记录到分析服务
analytics?.logEvent("performance_abnormal_exit",
value: backgroundExit.cumulativeAbnormalExitCount.formatted())
analytics?.logEvent("performance_cpu_exit",
value: backgroundExit.cumulativeCPUResourceLimitExitCount.formatted())
analytics?.logEvent("performance_memory_exit",
value: backgroundExit.cumulativeMemoryPressureExitCount.formatted())
analytics?.logEvent("performance_oom_exit",
value: backgroundExit.cumulativeMemoryResourceLimitExitCount.formatted())
}
// 前台退出数据
if let foregroundExit = exitMetrics.foregroundExitData {
print(" 前台退出:")
print(" 异常退出次数: \(foregroundExit.cumulativeAbnormalExitCount)")
print(" CPU 资源限制退出次数: \(foregroundExit.cumulativeCPUResourceLimitExitCount)")
print(" 内存压力退出次数: \(foregroundExit.cumulativeMemoryPressureExitCount)")
print(" 内存资源限制退出次数 (OOM): \(foregroundExit.cumulativeMemoryResourceLimitExitCount)")
}
}
// 应用响应性指标
if let responsivenessMetrics = payload.applicationResponsivenessMetrics {
print("应用响应性指标:")
print(" 卡顿次数: \(responsivenessMetrics.hangCount)")
}
// 内存指标
if let memoryMetrics = payload.memoryMetrics {
print("内存指标:")
print(" 峰值内存: \(memoryMetrics.peakMemoryUsage.value) MB")
print(" 平均内存: \(memoryMetrics.averageMemoryUsage.value) MB")
analytics?.logEvent("app_peak_memory",
value: memoryMetrics.peakMemoryUsage.value.formatted())
}
// CPU 指标
if let cpuMetrics = payload.cpuMetrics {
print("CPU 指标:")
print(" 累计 CPU 时间: \(cpuMetrics.cumulativeCPUTime.value) 秒")
print(" 平均 CPU 使用率: \(cpuMetrics.averageCPUUsage.value) %")
}
// 磁盘 IO 指标
if let diskIOMetrics = payload.diskIOMetrics {
print("磁盘 IO 指标:")
print(" 累计读取次数: \(diskIOMetrics.cumulativeLogicalWrites.value)")
print(" 累计写入次数: \(diskIOMetrics.cumulativeLogicalWrites.value)")
}
// 网络传输指标
if let networkMetrics = payload.networkTransferMetrics {
print("网络传输指标:")
print(" 累计上传数据: \(networkMetrics.cumulativeWifiUpload.value) bytes")
print(" 累计下载数据: \(networkMetrics.cumulativeWifiDownload.value) bytes")
}
}
print("\n=== 性能指标数据处理完成 ===\n")
}
// 接收诊断信息数据
nonisolated func didReceive(_ payloads: [MXDiagnosticPayload]) {
print("\n=== 收到诊断信息数据 ===")
print("数据包数量: \(payloads.count)")
for (index, payload) in payloads.enumerated() {
print("\n--- 诊断数据包 \(index + 1) ---")
print("时间范围: \(payload.timeStampBegin) 到 \(payload.timeStampEnd)")
// 崩溃诊断
if let crashes = payload.crashDiagnostics {
print("崩溃诊断数量: \(crashes.count)")
for (crashIndex, crash) in crashes.enumerated() {
print("\n 崩溃 \(crashIndex + 1):")
print(" 异常类型: \(crash.exceptionType.rawValue)")
print(" 异常代码: \(crash.exceptionCode)")
print(" 信号: \(crash.signal)")
print(" 终止原因: \(crash.terminationReason ?? "unknown")")
// 记录到分析服务
analytics?.logCrash(crash)
}
}
// CPU 异常诊断
if let cpuExceptions = payload.cpuExceptionDiagnostics {
print("CPU 异常诊断数量: \(cpuExceptions.count)")
for exception in cpuExceptions {
print(" CPU 异常类型: \(exception.exceptionType.rawValue)")
print(" CPU 异常代码: \(exception.exceptionCode)")
}
}
// 磁盘写入异常诊断
if let diskExceptions = payload.diskWriteExceptionDiagnostics {
print("磁盘写入异常诊断数量: \(diskExceptions.count)")
for exception in diskExceptions {
print(" 磁盘写入异常类型: \(exception.exceptionType.rawValue)")
print(" 磁盘写入异常代码: \(exception.exceptionCode)")
}
}
// 卡顿诊断
if let hangs = payload.hangDiagnostics {
print("卡顿诊断数量: \(hangs.count)")
for hang in hangs {
print(" 卡顿持续时间: \(hang.hangDuration) 秒")
}
}
}
print("\n=== 诊断信息数据处理完成 ===\n")
}
}
// 视图控制器
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
let label = UILabel()
label.text = "MetricKit Demo\n\n请查看控制台输出"
label.textAlignment = .center
label.numberOfLines = 0
label.font = .systemFont(ofSize: 18)
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
// 添加一个按钮来触发内存警告(用于测试)
let button = UIButton(type: .system)
button.setTitle("触发内存警告", for: .normal)
button.addTarget(self, action: #selector(triggerMemoryWarning), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 40)
])
}
@objc func triggerMemoryWarning() {
// 触发内存警告(仅用于测试)
NotificationCenter.default.post(name: UIApplication.didReceiveMemoryWarningNotification, object: nil)
print("已触发内存警告")
}
}
使用说明:
-
运行应用:运行这个 Demo 应用后,MetricKit 会自动订阅并开始收集性能指标和诊断信息。
-
查看数据:MetricKit 不会立即发送数据,系统可能会聚合数据并按日发送。你可以在控制台查看输出的数据。
-
测试场景:
- 让应用在后台运行一段时间,然后查看应用退出指标
- 触发内存警告,查看内存指标
- 让应用卡顿,查看卡顿诊断信息
-
实际项目集成:
- 将
ConsoleAnalytics替换为你自己的分析服务(如 Firebase Analytics) - 根据实际需求,选择需要监控的指标
- 将数据上传到自己的服务器进行分析
- 将
这个 Demo 展示了 MetricKit 的基本用法,你可以根据实际需求进行扩展和优化。