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

相关推荐
报错小能手3 小时前
ios开发方向——swift错误处理:do/try/catch、Result、throws
开发语言·学习·ios·swift
小夏子_riotous5 小时前
openstack的使用——5. Swift服务的基本使用
linux·运维·开发语言·分布式·云计算·openstack·swift
开心就好20258 小时前
Flutter iOS应用混淆与安全配置详细文档指南
后端·ios
mCell9 小时前
MacOS 下实现 AI 操控电脑(Computer Use)的思考
macos·agent·swift
开心就好202510 小时前
苹果iOS应用开发上架与推广完整教程
后端·ios
用户693717500138410 小时前
XChat 为什么选择 Rust 语言开发
android·前端·ios
MonkeyKing10 小时前
Objective-C Runtime 完整机制:objc_class /cache/bits 源码解析
前端·ios
用户794572239541310 小时前
【DGCharts】iOS 图表渲染事实标准——8 种图表类型、高度可定制,3 行代码画出一条折线
swiftui·swift
秋雨梧桐叶落莳12 小时前
【iOS】 AutoLayout初步学习
学习·macos·ios·objective-c·cocoa·xcode
chaoguo12341 天前
Any metadata 的内存布局
swift·metadata·value witness table