[iOS][idevice] Unity 工程 XCTest 内存图采集方案
在 idevice 中为 iOS 真机添加内存图(.memgraph)采集能力。经调研,物理设备无公开 CLI 等价于 Xcode「Debug Memory Graph」;Apple 官方路径为 XCTest + XCTMemoryMetric + xcodebuild -enablePerformanceTestsDiagnostics YES,再从 .xcresult 导出附件。
背景与结论
idevice 现状
IOSDevice3 已有 DVT(launch/pkill/proclist),无 memory / memgraph / footprint API
- pymobiledevice3
sysmon 可做 physFootprint 快照/流式监控,但不产出 .memgraph
leaks --outputGraph 仅适用于本机进程(含 iOS 模拟器),物理 iOS 真机不可用
选定方案
通过 XCTest 性能诊断采集 memgraph:
xcodebuild test \
-project <path> | -workspace <path> \
-scheme <scheme> \
-destination 'platform=iOS,id=<UDID>' \
-only-testing:<TestTarget>/<testMethod> \
-enablePerformanceTestsDiagnostics YES \
-resultBundlePath ./TestResults.xcresult
导出附件:
xcrun xcresulttool export attachments \
--path ./TestResults.xcresult \
--output-path ./memgraph_output
每次 measure 迭代通常产生 2 个 memgraph:pre_*.memgraph(测量前)和 post_*.memgraph(测量后)。
idevice 计划 API(待实现)
result = device.capture_memory_graph(
project=Path("Unity-iPhone.xcodeproj"),
scheme="Unity-iPhone",
test_filter="UnityMemoryUITests/testGameplayMemory",
)
# result.memgraphs -> pre/post .memgraph 文件列表
前提:调用方工程里已有带 XCTMemoryMetric 的性能测试;idevice 只负责跑测试并导出 memgraph,不生成测试代码。
XCTest 测试示例
func testGameplayMemory() throws {
let app = XCUIApplication()
let options = XCTMeasureOptions()
options.invocationOptions = [.manuallyStart]
measure(
metrics: [XCTMemoryMetric(application: app)],
options: options
) {
app.launch()
startMeasuring()
// 在此操作 App
}
}
要点:
- 测试内必须使用
XCTMemoryMetric
- 真机采集 memgraph;模拟器不可靠
- Scheme Diagnostics 可勾选 Malloc Stack Logging(有助于分配栈)
Unity 导出 Xcode 工程处理
结构
Unity-iPhone.xcodeproj
├── Unity-iPhone # 主 App(被测进程)
├── UnityFramework
└── ...
步骤
- Xcode 中
File → New → Target → UI Testing Bundle(如 UnityMemoryUITests)
- 写带
XCTMemoryMetric 的 measure(...) 测试
Edit Scheme → Test 勾选测试 target
- 本地
Product → Test 确认 .xcresult 含 memgraph
- 再用 idevice /
xcodebuild 自动化
Unity 重导出注意
Unity 重新 Build 可能覆盖手动加的 Test target。建议:PostProcessBuild 自动添加、或独立 workspace、或文档化「导出后需重新配置」。
替代 sleep:HTTP 轮询游戏状态
sleep(10) 可改为 HTTP 轮询,等游戏真正 ready 后再 startMeasuring():
func waitUntilGameReady(timeout: TimeInterval = 60) throws {
let url = URL(string: "http://127.0.0.1:17890/status")!
let deadline = Date().addingTimeInterval(timeout)
while Date() < deadline {
let (data, response) = try URLSession.shared.syncData(from: url)
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
Thread.sleep(forTimeInterval: 0.5)
continue
}
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
if json?["ready"] as? Bool == true { return }
Thread.sleep(forTimeInterval: 0.5)
}
XCTFail("Game not ready")
}
Unity 侧暴露 GET /status → {"ready": true, "scene": "MainMenu"}。
| 方式 |
优点 |
缺点 |
sleep |
简单 |
慢、不稳定 |
| HTTP 轮询 |
精确表达游戏状态 |
需游戏内 HTTP 服务;真机 127.0.0.1 需 POC |
| Accessibility 等待 |
真机最稳、XCTest 原生 |
Unity UI 需额外暴露 |
launchArguments 直达场景 |
跳过加载 |
需改启动逻辑 |
推荐组合:launchArguments 直达场景 + HTTP/Accessibility 确认加载完成 + startMeasuring()。
Unity UI 暴露给 XCTest(Accessibility)
XCTest 查询的是 iOS Accessibility 树(accessibilityLabel / accessibilityIdentifier),不是 GameObject.name。
方案 A:Unity 6 官方 Accessibility 模块(2023.2+)
using UnityEngine.Accessibility;
_hierarchy = new AccessibilityHierarchy();
_node = _hierarchy.AddNode("StartGame");
_node.role = AccessibilityRole.Button;
_node.label = "StartGame";
_node.invoked += () => button.onClick.Invoke();
AssistiveSupport.activeHierarchy = _hierarchy;
文档:Get started with screen reader support
注意:iOS 上可能仅在 VoiceOver 开启时层级才生效,真机 XCTest 需验证。
方案 B:测试专用状态标记(推荐)
不必暴露全部 UI,只暴露少量测试标记:
// Unity:主界面就绪
IOSAccessibility.SetStaticText("test_state", "MainMenuReady");
// XCTest
XCTAssertTrue(app.staticTexts["MainMenuReady"].waitForExistence(timeout: 60))
startMeasuring()
方案 C:原生 UIAccessibility 插件
各 Unity 版本可用,通过 iOS 插件设置 accessibilityIdentifier / accessibilityLabel / accessibilityTraits,坐标需从 RectTransform 转换到屏幕坐标。
XCTest 查询:
app.buttons["btn_battle"].tap() // identifier
app.staticTexts["MainMenuReady"].exists // label
推荐落地流程
Unity 导出 Xcode
↓
添加 UnityMemoryUITests + XCTMemoryMetric 测试
↓
(可选)Unity 暴露 Accessibility 标记 / HTTP 状态接口
↓
本地 Product → Test 确认 xcresult 含 memgraph
↓
idevice.capture_memory_graph(...) 自动化
↓
用 leaks / heap / vmmap 分析导出的 .memgraph
参考
[iOS][idevice] Unity 工程 XCTest 内存图采集方案
背景与结论
idevice 现状
IOSDevice3已有 DVT(launch/pkill/proclist),无 memory / memgraph / footprint APIsysmon可做physFootprint快照/流式监控,但不产出.memgraphleaks --outputGraph仅适用于本机进程(含 iOS 模拟器),物理 iOS 真机不可用选定方案
通过 XCTest 性能诊断采集 memgraph:
导出附件:
xcrun xcresulttool export attachments \ --path ./TestResults.xcresult \ --output-path ./memgraph_output每次
measure迭代通常产生 2 个 memgraph:pre_*.memgraph(测量前)和post_*.memgraph(测量后)。idevice 计划 API(待实现)
前提:调用方工程里已有带
XCTMemoryMetric的性能测试;idevice 只负责跑测试并导出 memgraph,不生成测试代码。XCTest 测试示例
要点:
XCTMemoryMetricUnity 导出 Xcode 工程处理
结构
步骤
File → New → Target → UI Testing Bundle(如UnityMemoryUITests)XCTMemoryMetric的measure(...)测试Edit Scheme → Test勾选测试 targetProduct → Test确认.xcresult含 memgraphxcodebuild自动化Unity 重导出注意
Unity 重新 Build 可能覆盖手动加的 Test target。建议:PostProcessBuild 自动添加、或独立 workspace、或文档化「导出后需重新配置」。
替代 sleep:HTTP 轮询游戏状态
sleep(10)可改为 HTTP 轮询,等游戏真正 ready 后再startMeasuring():Unity 侧暴露
GET /status→{"ready": true, "scene": "MainMenu"}。sleep127.0.0.1需 POClaunchArguments直达场景推荐组合:
launchArguments直达场景 + HTTP/Accessibility 确认加载完成 +startMeasuring()。Unity UI 暴露给 XCTest(Accessibility)
XCTest 查询的是 iOS Accessibility 树(
accessibilityLabel/accessibilityIdentifier),不是GameObject.name。方案 A:Unity 6 官方 Accessibility 模块(2023.2+)
文档:Get started with screen reader support
注意:iOS 上可能仅在 VoiceOver 开启时层级才生效,真机 XCTest 需验证。
方案 B:测试专用状态标记(推荐)
不必暴露全部 UI,只暴露少量测试标记:
方案 C:原生 UIAccessibility 插件
各 Unity 版本可用,通过 iOS 插件设置
accessibilityIdentifier/accessibilityLabel/accessibilityTraits,坐标需从RectTransform转换到屏幕坐标。XCTest 查询:
推荐落地流程
参考
enablePerformanceTestsDiagnostics)xctest(capture_memory_graphAPI 待实现)