iOS开发之MetricKit监控App性能

介绍

iOS 13 之后,Apple 推出了 MetricKit --- 一个由系统统一收集性能指标并按日自动送达给应用的强大框架。不需要侵入式埋点,不需要长期后台运行,也不需要手动分析复杂的系统行为,MetricKit 能够帮助开发者在真实用户设备上捕获 CPU、内存、启动耗时、异常诊断等关键性能指标。

特点

  • 自动收集:基于设备上的真实行为,系统会在后台定期收集性能数据。
  • 每天上报:每次应用启动,系统会把前一天的性能指标通过回调送达。
  • 极低侵入:性能统计由系统统一完成,不增加 CPU/内存负担,不影响用户体验。
  • 结构标准:系统提供结构化的 MXMetricPayload,便于解析与分析。
  • 隐私保护:Apple 会对数据进行匿名化和聚合处理,保护用户隐私。

步骤

  1. 导入 MetricKit。
  2. 注册 MetricKit 的订阅者。
  3. 实现回调协议,接受 Metric 数据。
  4. 逐项解析 Metric 数据。
  5. 上传 Metric 数据到服务器(可选)。

案例

以下是一份应用 MetricKit 的模版代码。

swift 复制代码
import MetricKit
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        MXMetricManager.shared.add(self)
        return true
    }

    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    func applicationWillTerminate(_ application: UIApplication) {
        MXMetricManager.shared.remove(self)
    }
}

// MARK: - MXMetricManagerSubscriber,回调协议
extension AppDelegate: MXMetricManagerSubscriber {
    // MARK: 接收每日性能指标
    func didReceive(_ payloads: [MXMetricPayload]) {
        for payload in payloads {
            handleMetrics(payload)
        }
    }

    // MARK: 接收诊断报告(崩溃、挂起等)
    func didReceive(_ payloads: [MXDiagnosticPayload]) {
        for payload in payloads {
            handleDiagnostic(payload)
        }
    }

    // MARK: 处理每日性能指标
    func handleMetrics(_ payload: MXMetricPayload) {
        print("===== 开始处理性能指标 =====")
        // 时间范围
        let formatter = DateFormatter()
        formatter.dateStyle = .short
        formatter.timeStyle = .short
        let timeRange = "\(formatter.string(from: payload.timeStampBegin)) - \(formatter.string(from: payload.timeStampEnd))"
        print("指标时间范围: \(timeRange)")

        // CPU指标
        if let cpu = payload.cpuMetrics {
            let cpuTime = cpu.cumulativeCPUTime.value
            print("CPU总使用时间: \(String(format: "%.2f", cpuTime)) 秒")
        }

        // GPU指标
        if let gpu = payload.gpuMetrics {
            let gpuTime = gpu.cumulativeGPUTime.value
            print("GPU总使用时间: \(String(format: "%.2f", gpuTime)) 秒")
        }

        // 内存指标
        if let memory = payload.memoryMetrics {
            let avgMemory = memory.averageSuspendedMemory.averageMeasurement.value
            let peakMemory = memory.peakMemoryUsage.value
            print("平均挂起内存: \(String(format: "%.2f", avgMemory / 1024 / 1024)) MB")
            print("峰值内存使用: \(String(format: "%.2f", peakMemory / 1024 / 1024)) MB")
        }

        // 启动时间指标
        if let launch = payload.applicationLaunchMetrics {
            let histogram = launch.histogrammedTimeToFirstDraw
            print("启动时间分布: ")
            for bucket in histogram.bucketEnumerator {
                if let bucket = bucket as? MXHistogramBucket<UnitDuration> {
                    let start = String(format: "%.2f", bucket.bucketStart.value)
                    let end = String(format: "%.2f", bucket.bucketEnd.value)
                    print("范围: \(start)-\(end)秒, 次数: \(bucket.bucketCount)")
                }
            }
        }

        // 上传数据
        let jsonData = payload.jsonRepresentation()
        uploadToServer(jsonData)
    }

    // MARK: 处理诊断报告
    func handleDiagnostic(_ payload: MXDiagnosticPayload) {
        print("===== 开始处理诊断报告 =====")

        // 崩溃诊断
        if let crashes = payload.crashDiagnostics {
            print("崩溃次数: \(crashes.count)")
            for (index, crash) in crashes.enumerated() {
                print("崩溃 \(index + 1): ")
                print("应用版本: \(crash.metaData.applicationBuildVersion)")
                print("设备类型: \(crash.metaData.deviceType)")
                print("系统版本: \(crash.metaData.osVersion)")
                print("平台架构: \(crash.metaData.platformArchitecture)")
                print("调用栈: \(crash.callStackTree)")
            }
        }

        // CPU异常
        if let cpuExceptions = payload.cpuExceptionDiagnostics {
            print("CPU异常次数: \(cpuExceptions.count)")
            for (index, exception) in cpuExceptions.enumerated() {
                print("CPU异常 \(index + 1): ")
                print("总CPU时间: \(exception.totalCPUTime.value) 秒")
                print("调用栈: \(exception.callStackTree)")
            }
        }

        // 上传数据
        let jsonData = payload.jsonRepresentation()
        uploadToServer(jsonData)
    }

    // MARK: 上传数据到服务器
    func uploadToServer(_ json: Data) {
        guard !json.isEmpty else { return }
        // 上传数据,如URLSession上传
    }
}
相关推荐
李游Leo6 小时前
HarmonyOS 7 实战开发 04:适配手机、折叠屏与大屏布局
ios·harmonyos
终端安全笔记6 小时前
iOS 27 强制 TLS 1.2:租赁设备的注册链路会在哪一环断
android·网络·安全·ios·智能手机
黑科技iOS上架6 小时前
iOS深度混淆flutter应用的最佳实践
flutter·ios·混淆·审核·深度混淆
思盛iOS签名上架7 小时前
IOS企业签名作用(核心作用一)
ios·testfight
黑科技iOS上架1 天前
unity3D 应用AppStore4.3被拒怎么自查
ios·审核·unity3d混淆
用户38034165882971 天前
FFmpeg 在 iOS 上的裁剪、集成与包体控制
ios·音视频开发
黑科技iOS上架1 天前
Cocos应用App Store4.3被拒怎么自查
macos·ios·objective-c·cocoa·审核·ios混淆·cocos混淆
00后程序员张1 天前
怎么用 Egret(白鹭引擎)打包 iOS 应用并上架 App Store?
android·ios·小程序·https·uni-app·iphone·webview
风雨_1 天前
show-me + grill-me 结合使用总结
ios
东坡肘子1 天前
iPhone Duo 带来的机遇与挑战 -- 肘子的 Swift 周报 #153
人工智能·swiftui·swift