欢迎光临
我们一直在努力

使用 MetricKit 监控应用性能

网罗开发
(小红书、快手、视频号同名)

  大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。

图书作者:《ESP32-C3 物联网工程开发实战》 图书作者:《SwiftUI 入门,进阶与实战》 超级个体:COC上海社区主理人 特约讲师:大学讲师,谷歌亚马逊分享嘉宾 科技博主:华为HDE/HDG

我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。

展菲:您的前沿技术领航员 👋 大家好,我是展菲! 📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。 📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。 💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。 📅 最新动态:2025 年 3 月 17 日 快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!

文章目录

    • 前言
    • Xcode Organizer 的局限性
      • Organizer 提供的信息
      • Organizer 的不足之处
    • MetricKit 框架介绍
      • MetricKit 的核心组件
    • 如何集成 MetricKit
      • 第一步:定义分析接口
      • 第二步:实现分析服务
      • 第三步:在 AppDelegate 中集成 MetricKit
    • 代码详细分析
      • MXMetricManager 的使用
      • MXMetricManagerSubscriber 协议
      • MXMetricPayload 详解
      • MXDiagnosticPayload 详解
      • 处理应用退出指标
      • 处理崩溃诊断信息
      • 时间戳信息
    • 实际应用场景
      • 场景一:监控应用终止原因
      • 场景二:监控应用启动性能
      • 场景三:监控内存使用情况
      • 场景四:监控应用卡顿情况
      • 场景五:构建自定义性能监控面板
    • 注意事项和最佳实践
      • 数据收集的时机
      • 数据量控制
      • 隐私和安全
      • 性能影响
    • 总结
    • 完整可运行 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 其实挺简单的,主要分为几个步骤:

    第一步:定义分析接口

    首先,我们需要定义一个分析接口,用于记录性能指标和崩溃信息:

    protocol Analytics {
    func logEvent(_ name: String, value: String)
    func logCrash(_ crash: MXCrashDiagnostic)
    }

    这个接口定义了两个方法:

    • logEvent:记录性能事件,比如应用终止次数、CPU 退出次数等
    • logCrash:记录崩溃信息,包括崩溃堆栈、异常类型等

    第二步:实现分析服务

    接下来,我们需要实现这个接口。在实际项目中,你可以集成 Firebase Analytics、Mixpanel、或者自己的后端服务:

    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:

    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 的使用

    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:前台退出数据(类似后台退出数据)
  • 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:卡顿诊断信息

    • 卡顿持续时间
    • 调用堆栈信息
  • 处理应用退出指标

    在我们的代码中,我们主要关注应用退出指标:

    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)
  • 这些信息可以帮助我们了解应用被系统终止的原因。

    处理崩溃诊断信息

    if let crashes = payload.crashDiagnostics {
    for crash in crashes {
    analytics?.logCrash(crash)
    }
    }

    这段代码的作用是:

  • 检查是否有崩溃诊断信息
  • 遍历所有崩溃信息
  • 记录每个崩溃的详细信息
  • 崩溃信息包括异常类型、异常代码、信号、终止原因和调用堆栈等,这些信息对于定位崩溃原因非常重要。

    时间戳信息

    MXMetricPayload 和 MXDiagnosticPayload 都提供了 timeStampBegin 和 timeStampEnd 属性,让我们可以知道每个数据包覆盖的时间范围:

    let timeRange = payload.timeStampBeginpayload.timeStampEnd
    print("数据时间范围: \\(timeRange)")

    这对于分析性能趋势非常有用。

    实际应用场景

    MetricKit 在实际项目中的应用场景非常广泛,让我们看看几个常见的应用场景:

    场景一:监控应用终止原因

    应用被系统终止是一个常见的问题,但很难定位原因。通过 MetricKit,我们可以详细记录应用终止的原因:

    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,我们可以监控应用启动性能:

    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,我们可以监控内存使用情况:

    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,我们可以监控应用卡顿情况:

    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,我们可以构建自己的性能监控面板,将收集到的数据上传到自己的服务器:

    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:

    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 的基本用法,你可以根据实际需求进行扩展和优化。

    赞(0)
    未经允许不得转载:171主机测评 » 使用 MetricKit 监控应用性能
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址