使用 MetricKit 监控应用性能


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

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

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

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

展菲:您的前沿技术领航员

👋 大家好,我是展菲!

📱 全网搜索"展菲",即可纵览我在各大平台的知识足迹。

📣 公众号"Swift社区",每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。

💬 微信端添加好友"fzhanfei",与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。

📅 最新动态:2025 年 3 月 17 日

快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!

文章目录

前言

最近在做一个 iOS 项目的时候,遇到了一个让人头疼的问题:应用在用户设备上经常被系统终止,但我们在 Xcode Organizer 里看到的崩溃报告信息不够详细,很难定位问题。Xcode Organizer 确实提供了很多有用的性能指标,比如崩溃、能耗影响、卡顿、启动时间、内存消耗和应用终止等,但对于某些问题,特别是应用终止的原因,它提供的信息还不够充分。

为了解决这个问题,Apple 推出了 MetricKit 框架,让我们可以收集更详细的诊断信息,并构建自己的性能监控面板。今天我们就来聊聊如何使用 MetricKit 来监控应用性能,以及如何在实际项目中应用它。

Xcode Organizer 的局限性

Xcode Organizer 确实是一个很好的工具,它提供了很多有用的性能指标。但有时候,我们需要更详细的信息来解决问题。

Organizer 提供的信息

Xcode Organizer 可以显示:

  1. 崩溃报告:应用崩溃时的堆栈信息
  2. 能耗影响:应用对设备电池的影响
  3. 卡顿:应用主线程卡顿的情况
  4. 启动时间:应用启动所需的时间
  5. 内存消耗:应用使用的内存情况
  6. 应用终止:应用被系统终止的次数

这些信息确实很有用,但对于某些问题,特别是应用终止的原因,信息还不够详细。

Organizer 的不足之处

比如,当应用被系统终止时,Organizer 可能只会告诉你"应用被终止了",但不会告诉你:

  • 是因为内存不足被终止的吗?
  • 是因为 CPU 资源限制被终止的吗?
  • 是因为内存压力被终止的吗?
  • 是因为 OOM(Out of Memory)被终止的吗?

这些信息对于定位问题非常重要,但 Organizer 提供的信息不够详细。

MetricKit 框架介绍

MetricKit 是 Apple 在 iOS 13 中引入的一个框架,它让我们可以收集更详细的性能指标和诊断信息。通过 MetricKit,我们可以:

  1. 收集详细的性能指标:包括启动时间、内存使用、CPU 使用等
  2. 收集诊断信息:包括崩溃报告、CPU 异常、磁盘写入异常等
  3. 构建自定义的性能监控面板:将收集到的数据上传到自己的服务器,进行分析和展示

MetricKit 的核心组件

MetricKit 主要有以下几个核心组件:

  1. MXMetricManager:管理性能指标的收集和分发
  2. MXMetricPayload:包含性能指标数据
  3. MXDiagnosticPayload:包含诊断信息数据
  4. 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 协议定义了两个可选方法:

  1. didReceive(_ payloads: [MXMetricPayload]):当系统收集到性能指标时调用
  2. didReceive(_ payloads: [MXDiagnosticPayload]):当系统收集到诊断信息时调用

这两个方法都标记为 nonisolated,这意味着它们可以在任何线程上调用,不需要在主线程上执行。

MXMetricPayload 详解

MXMetricPayload 包含了各种性能指标数据,它继承自 MXMetric 抽象类。主要包含以下属性:

  1. applicationLaunchMetrics:应用启动相关的指标

    • 启动时间
    • 启动时的内存使用
    • 启动时的 CPU 使用
  2. applicationExitMetrics:应用退出相关的指标

    • backgroundExitData :后台退出数据
      • cumulativeAbnormalExitCount:累计异常退出次数
      • cumulativeCPUResourceLimitExitCount:累计 CPU 资源限制退出次数
      • cumulativeMemoryPressureExitCount:累计内存压力退出次数
      • cumulativeMemoryResourceLimitExitCount:累计内存资源限制退出次数(OOM)
    • foregroundExitData:前台退出数据(类似后台退出数据)
  3. applicationTimeMetrics:应用运行时间相关的指标

    • 前台运行时间
    • 后台运行时间
  4. applicationResponsivenessMetrics:应用响应性相关的指标

    • 卡顿次数
    • 卡顿持续时间
  5. memoryMetrics:内存使用相关的指标

    • 峰值内存使用
    • 平均内存使用
  6. cpuMetrics:CPU 使用相关的指标

    • 峰值 CPU 使用
    • 平均 CPU 使用
  7. diskIOMetrics:磁盘 IO 相关的指标

    • 读取次数
    • 写入次数
    • 读取数据量
    • 写入数据量
  8. networkTransferMetrics:网络传输相关的指标

    • 上传数据量
    • 下载数据量

MXDiagnosticPayload 详解

MXDiagnosticPayload 包含了各种诊断信息数据,它继承自 MXDiagnostic 抽象类。主要包含以下属性:

  1. crashDiagnostics:崩溃诊断信息

    • exceptionType:异常类型
    • exceptionCode:异常代码
    • signal:信号
    • terminationReason:终止原因
    • virtualMemoryRegionInfo:虚拟内存区域信息
    • callStackTree:调用堆栈树
  2. cpuExceptionDiagnostics:CPU 异常诊断信息

    • CPU 异常类型
    • CPU 异常代码
    • 调用堆栈信息
  3. diskWriteExceptionDiagnostics:磁盘写入异常诊断信息

    • 磁盘写入异常类型
    • 磁盘写入异常代码
    • 调用堆栈信息
  4. 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()
    )
}

这段代码的作用是:

  1. 检查是否有后台退出数据
  2. 记录异常退出次数
  3. 记录 CPU 资源限制退出次数
  4. 记录内存压力退出次数
  5. 记录内存资源限制退出次数(OOM)

这些信息可以帮助我们了解应用被系统终止的原因。

处理崩溃诊断信息

swift 复制代码
if let crashes = payload.crashDiagnostics {
    for crash in crashes {
        analytics?.logCrash(crash)
    }
}

这段代码的作用是:

  1. 检查是否有崩溃诊断信息
  2. 遍历所有崩溃信息
  3. 记录每个崩溃的详细信息

崩溃信息包括异常类型、异常代码、信号、终止原因和调用堆栈等,这些信息对于定位崩溃原因非常重要。

时间戳信息

MXMetricPayloadMXDiagnosticPayload 都提供了 timeStampBegintimeStampEnd 属性,让我们可以知道每个数据包覆盖的时间范围:

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 不会立即发送数据,系统可能会聚合数据并按日发送。在极少数情况下,可能会更频繁地发送,但你不应该依赖任何特定的时间表。

这意味着:

  1. 数据可能会有延迟,不是实时的
  2. 数据是聚合的,不是每次事件都会发送
  3. 不要依赖特定的发送频率

数据量控制

MetricKit 收集的数据量可能很大,特别是崩溃诊断信息。在上传数据时,要注意:

  1. 压缩数据:在上传前压缩数据,减少网络传输量
  2. 批量上传:将多个数据包批量上传,减少网络请求次数
  3. 错误处理:处理上传失败的情况,可能需要重试机制

隐私和安全

MetricKit 收集的数据可能包含敏感信息,比如调用堆栈、内存内容等。在处理这些数据时,要注意:

  1. 数据加密:在上传数据时使用 HTTPS,确保数据传输安全
  2. 数据脱敏:在存储数据时,考虑对敏感信息进行脱敏处理
  3. 用户同意:确保用户同意收集这些数据,符合隐私政策

性能影响

MetricKit 的数据收集和处理可能会影响应用性能,要注意:

  1. 异步处理:在后台线程处理数据,不要阻塞主线程
  2. 延迟处理:如果数据量很大,可以考虑延迟处理,在应用空闲时处理
  3. 资源限制:注意内存和 CPU 使用,避免影响应用性能

总结

MetricKit 是一个强大的工具,它填补了 Xcode Organizer 的空白,让我们可以深入了解应用在真实环境中的表现。通过订阅 MXMetricManager 并处理 MXMetricPayloadMXDiagnosticPayload,我们可以获得应用启动、终止、崩溃和资源使用等方面的可见性,这些信息在其他情况下很难或不可能完全理解。

在实际项目中,MetricKit 可以帮助我们:

  1. 定位问题:通过详细的性能指标和诊断信息,快速定位问题
  2. 优化性能:通过监控性能指标,发现性能瓶颈并优化
  3. 提升用户体验:通过监控应用启动时间、卡顿等情况,提升用户体验
  4. 构建监控系统:构建自己的性能监控面板,更好地管理应用性能

虽然 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("已触发内存警告")
    }
}

使用说明:

  1. 运行应用:运行这个 Demo 应用后,MetricKit 会自动订阅并开始收集性能指标和诊断信息。

  2. 查看数据:MetricKit 不会立即发送数据,系统可能会聚合数据并按日发送。你可以在控制台查看输出的数据。

  3. 测试场景

    • 让应用在后台运行一段时间,然后查看应用退出指标
    • 触发内存警告,查看内存指标
    • 让应用卡顿,查看卡顿诊断信息
  4. 实际项目集成

    • ConsoleAnalytics 替换为你自己的分析服务(如 Firebase Analytics)
    • 根据实际需求,选择需要监控的指标
    • 将数据上传到自己的服务器进行分析

这个 Demo 展示了 MetricKit 的基本用法,你可以根据实际需求进行扩展和优化。

相关推荐
LawrenceMssss3 小时前
由于创建一个完整的App涉及到多个层面(如前端、后端、数据库等),并且每种语言通常有其特定的用途(如Java/Kotlin用于Android开发,Swift/Objective-C用于iOS开发,Py
android·java·ios
快手技术6 小时前
KwaiDesign:为快手多元业务打造统一、高效的设计与开发体系
swiftui·arkui·weui
2501_915921437 小时前
如何在苹果手机上面进行抓包?iOS代理抓包,数据流抓包
android·ios·智能手机·小程序·uni-app·iphone·webview
Boyang_7 小时前
在 iOS 26 上@property 的一个小 bug
ios
Swift社区8 小时前
LeetCode 374 猜数字大小 - Swift 题解
算法·leetcode·swift
七牛云行业应用8 小时前
iOS 19.3 突发崩溃!Gemini 3 导致 JSON 解析失败的紧急修复
人工智能·ios·swift·json解析·大模型应用
初级代码游戏8 小时前
iOS开发 SwiftUI 6 :List
ios·swiftui·swift
00后程序员张9 小时前
iOS APP 性能测试工具,监控CPU,实时日志输出
android·ios·小程序·https·uni-app·iphone·webview
Digitally9 小时前
如何在电脑上轻松使用 iPhone 作为 U 盘
ios·电脑·iphone