性能分析
性能分析-工具
开源工具
- FLEX (Flipboard Explorer) 是一个非常受欢迎的开源 iOS 内部工具,提供了强大的 UI,允许开发者在运行时检查应用的视图层次结构、网络请求、数据库、用户偏好设置等。适合开发者在应用内实时调试和检查性能问题。
- XCTest XCTest 是 Apple 官方的单元测试框架,支持性能测试。开发者可以通过 measure 方法来衡量代码块的执行时间,从而发现性能瓶颈。适合需要在单元测试中添加性能测试的场景。
- KSCrash KSCrash 是一个强大的崩溃报告框架,它不仅能够捕获崩溃信息,还能提供应用程序的性能数据,例如内存使用和 CPU 使用情况。适合需要深入了解崩溃原因并监控相关性能数据的场景。
- GT (GDT, GodEye) GodEye 是一个开源的 iOS 性能监控工具包,提供了多种监控功能,包括 FPS、内存使用、CPU 使用率、网络请求、崩溃日志等。它有一个方便的 UI,可以实时显示性能数据。适合在开发过程中嵌入应用进行实时性能监控。
- libimobiledevice libimobiledevice 是一个开源的库,提供了与 iOS 设备交互的 API,可以用来监控设备状态和性能,特别是对非越狱设备进行操作。
Instruments
Instruments 是 Xcode 自带的一款强大的性能分析和调试工具,广泛用于 iOS 应用的性能监测和问题排查。
打开 Xcode 项目,选择 Product > Profile,或者使用快捷键 Command + I。这会编译并启动应用,然后打开 Instruments 界面。你也可以直接从 Xcode 中选择 Xcode > Open Developer Tool > Instruments 启动 Instruments。
Instruments 提供了多种模板,每个模板用于不同类型的性能分析。常用的模板有:
- Time Profiler:用于分析 CPU 使用情况,找出性能瓶颈。
- Allocations:监控内存分配,帮助识别内存泄漏和过度使用。
- Leaks:检测内存泄漏。
- Core Animation:分析应用的动画性能,找出导致掉帧的原因。
- Energy Log:评估应用的能耗表现。
当应用运行时,正常使用它或执行你需要分析的特定操作。Instruments 将记录这些操作期间的性能数据。点击 Record 按钮(通常是红色圆点),开始记录数据。完成后,点击 Stop 按钮停止记录。Instruments 会生成一个时间轴,展示应用在运行期间的各种性能数据。你可以通过拖动时间轴,放大或缩小某段时间范围内的数据。
细节分析:
- CPU 使用情况:使用 Time Profiler 查看函数调用栈,识别性能瓶颈。
- 内存使用情况:使用 Allocations 或 Leaks 查看对象分配情况,分析内存泄漏和不当的内存使用。
- 帧率分析:使用 Core Animation 检查应用的帧率,查找导致掉帧的操作。
你可以在关键点添加标记(Mark),帮助更容易地定位和分析问题。
System Trace
System Trace 是一个强大的工具,可以帮助你深入分析和解决性能问题。System Trace 是 Xcode Instruments 中的一个模板,它提供了关于系统性能的详细信息,包括 CPU 使用情况、线程活动、系统调用、分页错误、I/O 接口信息等,甚至包括进程内以及进程间的情况。以下是 System Trace 的使用方法:
步骤一:准备工作
- 确保 Xcode 和设备准备好:确保你已经安装了最新版本的 Xcode,并将 iOS 设备连接到你的 Mac。
- 打开 Xcode Instruments:在 Xcode 中选择 Product > Profile,或者直接在 Finder 中打开 Instruments 应用。
步骤二:选择 System Trace 模板
- 选择模板:在 Instruments 中,打开“选择模板”窗口,选择 System Trace 模板。
- 连接设备:选择你要分析的 iOS 设备,确保设备处于调试模式并已连接。
步骤三:开始记录
- 启动记录:点击红色的记录按钮,开始采集数据。运行你的应用程序,并重现卡顿或性能问题。
- 结束记录:当你捕获到问题或完成测试后,点击停止按钮停止记录。
步骤四:分析数据
- 查看时间线:System Trace 会生成一个时间线视图,其中包括 CPU 使用情况、线程活动、硬件中断等。你可以通过缩放和滚动来查看不同时间点的详细信息。
- 分析线程活动:查看哪些线程在特定时间段内占用了大量 CPU 资源,找出是否存在长时间运行的任务或死锁。
- 查看内核事件:System Trace 还会显示内核相关的事件,如中断处理和内存管理,这对于深入分析系统级问题非常有帮助。
- 标记和过滤:使用标记和过滤功能,专注于特定时间段或线程,以更容易地识别问题。
步骤五:优化和调试
- 识别问题代码:根据分析结果,找到可能导致卡顿的代码段,查看是否有可以优化的部分。
- 修改代码并重新测试:修改代码后,重新运行 System Trace 来验证优化是否解决了性能问题。
步骤六:生成报告
- 保存和分享:你可以将 System Trace 生成的分析数据导出为 .trace 文件,方便共享或进一步分析。
- 对比分析:可以多次运行 System Trace,比较优化前后的数据,确保性能得到改善。
MetricKit
基本使用
MetricKit 允许开发者收集和分析与应用程序性能、崩溃、功耗等相关的关键指标。以下是使用 MetricKit 的方法:
首先,在你的项目中导入 MetricKit 框架:
1 | import MetricKit |
创建一个 MXMetricManager 的实例,它是收集指标数据的主要接口。
1 | let metricManager = MXMetricManager.shared |
注册 Metrics 管理器
你需要让你的类(通常是 AppDelegate 或其他适合的单例类)遵循 MXMetricManagerSubscriber 协议,并注册为 MXMetricManager 的订阅者。
1 | class AppDelegate: UIResponder, UIApplicationDelegate, MXMetricManagerSubscriber { |
分析和存储指标数据
MXMetricPayload 包含了多种指标数据,如 CPU 使用率、内存使用情况、网络流量等。你可以遍历这些数据,存储或发送到你的后端进行进一步分析。
1 | func didReceive(_ payloads: [MXMetricPayload]) { |
处理崩溃诊断数据
MetricKit 也可以收集应用的崩溃诊断信息,方便你追踪和解决崩溃问题。
1 | func didReceive(_ payloads: [MXCrashDiagnosticPayload]) { |
从 Metric Manager 移除订阅者
如果你不再需要收集指标数据,可以从 MXMetricManager 中移除你的订阅者。
1 | MXMetricManager.shared.remove(self) |
MXMetricManager 只在实际设备上运行时才会收集数据,模拟器中不会生成有效的数据。MetricKit 数据的收集是异步的,且系统会自动决定何时发送数据给你的应用,所以你不会立即收到数据。数据的保留期较短,Apple 建议尽快处理和存储接收到的指标数据。
自定义和收集特定的应用指标
虽然 MetricKit 本身主要提供了一组预定义的指标,如启动时间、CPU 使用率、内存使用情况等,但你也可以通过结合其他技术手段来自定义和收集特定的应用指标。
思路
- 利用现有的 MetricKit 指标: 如果你的自定义指标能够与 MetricKit 提供的现有指标关联,最好优先利用这些指标。例如,你可以利用 MXAppLaunchMetric 来测量特定场景下的启动时间。
- 结合日志系统(如 os_log): 你可以通过 os_log 系统记录特定的自定义事件和数据,然后在 MXLogHandle 提供的日志中分析这些自定义指标。虽然这不是直接通过 MetricKit 记录,但你可以通过这些日志生成自定义报告。
- 使用第三方工具或库: 如果 MetricKit 无法满足需求,可以使用其他分析工具或库来收集和发送自定义指标,例如 Firebase Analytics 或 Amplitude。
- 自定义代码实现: 你可以在应用中自行编写代码,定期收集并记录自定义指标数据,然后通过网络请求或其他方式将其发送到后端服务器进行分析。
假设你想自定义一个指标,用于监测用户在某个特定功能页面的停留时间。你可以通过以下方式实现:
在 ViewController 中记录开始时间和结束时间
1 | import UIKit |
定期发送收集到的数据。你可以通过定时任务或应用进入后台时,将收集到的自定义指标发送到服务器或分析系统。
1 | func applicationDidEnterBackground(_ application: UIApplication) { |
将自定义指标与 MetricKit 集成。虽然 MetricKit 没有直接支持自定义指标的功能,但你可以通过 MXMetricManager 的代理方法捕获系统提供的指标数据,并结合自定义指标进行分析。
1 | import MetricKit |
InApp分析工具
iOS 平台上常用的 In-app Debug 工具有以下几种:
- Flex 是一个功能强大的 In-app Debug 工具,允许开发者在应用内实时查看和修改视图层次结构、网络请求、用户默认设置等。它还支持动态调整 UI 以及调试其他 app 内部逻辑。无需重新编译代码即可直接调试;可以修改内存中的值来观察变化。
- Chisel 是 Facebook 开发的一组 LLDB 命令集,专门用于在调试时提供更方便的操作。它能帮助开发者快速检查视图层次结构、查看控件信息等。与 Xcode LLDB 无缝集成,通过命令行调试视图、打印出布局相关信息等。
- Reveal 是一个图形化的 In-app Debug 工具,它允许开发者在运行中的应用中实时查看和编辑视图层次结构,支持 2D 和 3D 的视图展示。提供直观的 UI 调试界面,可以轻松地查看和修改视图属性;支持 iOS 和 tvOS。
- Lookin 是一个开源的 iOS 视觉调试工具,专门用于分析和检查 iOS 应用的界面结构。它提供类似于 Xcode 的 View Debugging 功能,但更加灵活和强大,尤其是在复杂 UI 布局的分析上。通过 Lookin,你可以轻松地获取 iOS 应用中的界面层级、布局信息,并进行实时的 UI 调试和调整。可以称之为开源版的 Reveal。
fishhook
fishhook 是一个用于在 iOS 和 macOS 环境中进行函数替换的开源库,主要用于动态链接库(dylib)中的符号重写。
fishhook 通过操作 Mach-O 文件的符号表来找到需要替换的函数。Mach-O 文件的符号表包含了所有函数和变量的符号(名称和地址)信息。fishhook 使用 dyld(动态链接器)的相关函数来进行符号重定向。它首先使用 dyld 提供的接口找到符号的地址,然后将这些符号的地址替换为新的地址。通过修改 Mach-O 文件中的符号表来完成函数替换。这包括修改动态链接库中的 __dyld 的符号信息,使得在运行时调用这些符号时会跳转到新的实现。
fishhook 允许在程序运行时动态替换函数实现,这对于调试和修改第三方库的行为非常有用。
以下是一个简单的示例,展示了如何使用 fishhook 来替换函数实现:
1 |
|
在这个示例中,replace_printf 函数将 printf 函数的实现替换为 new_printf,并在控制台上输出 “Intercepted: “。
Frida
使用 Frida 来进行动态分析和调试
- 准备工作
- Frida 工具链: 安装 Frida 的命令行工具。在 macOS 上可以通过 pip 安装:
bash pip install frida-tools
- 开发者证书: 需要一个有效的苹果开发者账号,用于签名自定义的 Frida 服务器应用。
- 编译并签名 Frida Server
- 下载 Frida 源码: 从 Frida 的 GitHub 仓库克隆源码。
1 | git clone https://github.com/frida/frida.git |
- 编译 Frida Server: 使用 Frida 提供的构建脚本来编译 Frida Server。这个步骤可能需要一些配置,具体过程可以参考 Frida 官方文档。编-译完成后,你会得到一个 frida-server 可执行文件。
- 使用开发者证书签名: 将编译好的 frida-server 文件进行签名,确保它可以在非越狱的设备上运行。
1 | codesign -s "iPhone Developer: Your Name (Team ID)" --entitlements entitlements.plist frida-server |
entitlements.plist 文件需要包括允许调试和 taskforpid 权限的配置。
- 部署 Frida Server 到设备
- 使用 Xcode 部署: 将 frida-server 文件打包成一个应用,然后通过 Xcode 部署到目标设备上。
- 或者通过 SSH 部署: 如果你已经有其他方式在非越狱设备上运行自签名应用,可以通过 SSH 将 frida-server 直接拷贝到设备上。
- 启动 Frida Server
- 在设备上启动: 打开你部署的应用,确保 frida-server 在后台运行。你可以通过 Xcode 的调试控制台或终端命令来启动它。
- 连接 Frida Server: 在你的电脑上通过 USB 连接设备,并使用 Frida CLI 连接到运行中的 Frida Server。
1 | frida-ps -U |
- 开始调试: 你可以使用 Frida 的各种命令来分析和调试目标应用。
- Frida CLI 使用示例
- 列出所有进程:
1 | frida-ps -U |
- 附加到目标进程:
1 | frida -U -n <AppName> |
- 运行脚本:
1 | frida -U -n <AppName> -l script.js |