axiom-hang-diagnostics
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHang Diagnostics
卡顿诊断
Systematic diagnosis and resolution of app hangs. A hang occurs when the main thread is blocked for more than 1 second, making the app unresponsive to user input.
应用卡顿的系统化诊断与解决方法。当主线程被阻塞超过1秒,导致应用无法响应用户输入时,就会发生卡顿。
Red Flags — Check This Skill When
预警信号 — 何时使用本技能
| Symptom | This Skill Applies |
|---|---|
| App freezes briefly during use | Yes — likely hang |
| UI doesn't respond to touches | Yes — main thread blocked |
| "App not responding" system dialog | Yes — severe hang |
| Xcode Organizer shows hang diagnostics | Yes — field hang reports |
| MetricKit MXHangDiagnostic received | Yes — aggregated hang data |
| Animations stutter or skip | Maybe — could be hitch, not hang |
| App feels slow but responsive | No — performance issue, not hang |
| 症状 | 是否适用本技能 |
|---|---|
| 应用在使用过程中短暂冻结 | 是 — 可能是卡顿 |
| UI无法响应触摸操作 | 是 — 主线程被阻塞 |
| 弹出“应用无响应”系统对话框 | 是 — 严重卡顿 |
| Xcode Organizer显示卡顿诊断信息 | 是 — 线上卡顿报告 |
| 收到MetricKit的MXHangDiagnostic数据 | 是 — 聚合的卡顿数据 |
| 动画卡顿或跳帧 | 可能 — 可能是帧卡顿(hitch),而非主线程卡顿 |
| 应用运行缓慢但仍可响应 | 否 — 属于性能问题,而非卡顿 |
What Is a Hang
什么是卡顿
A hang is when the main runloop cannot process events for more than 1 second. The user taps, but nothing happens.
User taps → Main thread busy/blocked → Event queued → 1+ second delay → HANGKey distinction: The main thread handles ALL user input. If it's busy or blocked, the entire UI freezes.
卡顿指主运行循环无法处理事件超过1秒的情况。用户点击后,没有任何响应。
用户点击 → 主线程繁忙/阻塞 → 事件排队 → 延迟1秒以上 → 卡顿关键区别:主线程负责处理所有用户输入。如果主线程繁忙或被阻塞,整个UI都会冻结。
Hang vs Hitch vs Lag
卡顿 vs 帧卡顿 vs 延迟
| Issue | Duration | User Experience | Tool |
|---|---|---|---|
| Hang | >1 second | App frozen, unresponsive | Time Profiler, System Trace |
| Hitch | 1-3 frames (16-50ms) | Animation stutters | Animation Hitches instrument |
| Lag | 100-500ms | Feels slow but responsive | Time Profiler |
This skill covers hangs. For hitches, see . For general lag, see .
axiom-swiftui-performanceaxiom-performance-profiling| 问题类型 | 持续时间 | 用户体验 | 工具 |
|---|---|---|---|
| 卡顿 | >1秒 | 应用冻结,无响应 | Time Profiler, System Trace |
| 帧卡顿(Hitch) | 1-3帧(16-50毫秒) | 动画卡顿 | Animation Hitches工具 |
| 延迟(Lag) | 100-500毫秒 | 运行缓慢但仍可响应 | Time Profiler |
本技能针对卡顿问题。帧卡顿相关内容请参考。通用性能延迟问题请参考。
axiom-swiftui-performanceaxiom-performance-profilingThe Two Causes of Hangs
卡顿的两类原因
Every hang has one of two root causes:
所有卡顿都源于以下两类根本原因:
1. Main Thread Busy
1. 主线程繁忙
The main thread is doing work instead of processing events.
Subcategories:
| Type | Example | Fix |
|---|---|---|
| Proactive work | Pre-computing data user hasn't requested | Lazy initialization, compute on demand |
| Irrelevant work | Processing all notifications, not just relevant ones | Filter notifications, targeted observers |
| Suboptimal API | Using blocking API when async exists | Switch to async API |
主线程在执行任务,而非处理事件。
细分类型:
| 类型 | 示例 | 修复方案 |
|---|---|---|
| 主动执行的非必要任务 | 预计算用户尚未请求的数据 | 延迟初始化,按需计算 |
| 无关任务处理 | 处理所有通知,而非仅相关通知 | 过滤通知,使用定向观察者 |
| 非最优API使用 | 在有异步API的情况下使用阻塞式API | 切换为异步API |
2. Main Thread Blocked
2. 主线程被阻塞
The main thread is waiting for something else.
Subcategories:
| Type | Example | Fix |
|---|---|---|
| Synchronous IPC | Calling system service synchronously | Use async API variant |
| File I/O | | Move to background queue |
| Network | Synchronous URL request | Use URLSession async |
| Lock contention | Waiting for lock held by background thread | Reduce critical section, use actors |
| Semaphore/dispatch_sync | Blocking on background work | Restructure to async completion |
主线程在等待其他资源。
细分类型:
| 类型 | 示例 | 修复方案 |
|---|---|---|
| 同步IPC | 同步调用系统服务 | 使用异步API变体 |
| 文件I/O | 在主线程调用 | 移至后台队列执行 |
| 网络请求 | 同步URL请求 | 使用URLSession异步接口 |
| 锁竞争 | 等待后台线程持有的锁 | 缩小临界区范围,使用Actor |
| 信号量/dispatch_sync | 阻塞等待后台任务完成 | 重构为异步回调方式 |
Decision Tree — Diagnosing Hangs
卡顿诊断决策树
START: App hangs reported
│
├─→ Do you have hang diagnostics from Organizer or MetricKit?
│ │
│ ├─→ YES: Examine stack trace
│ │ │
│ │ ├─→ Stack shows your code running
│ │ │ → BUSY: Main thread doing work
│ │ │ → Profile with Time Profiler
│ │ │
│ │ └─→ Stack shows waiting (semaphore, lock, dispatch_sync)
│ │ → BLOCKED: Main thread waiting
│ │ → Profile with System Trace
│ │
│ └─→ NO: Can you reproduce?
│ │
│ ├─→ YES: Profile with Time Profiler first
│ │ │
│ │ ├─→ High CPU on main thread
│ │ │ → BUSY: Optimize the work
│ │ │
│ │ └─→ Low CPU, thread blocked
│ │ → Use System Trace to find what's blocking
│ │
│ └─→ NO: Enable MetricKit in app
│ → Wait for field reports
│ → Check Organizer > Hangs开始:收到应用卡顿报告
│
├─→ 是否有来自Organizer或MetricKit的卡顿诊断数据?
│ │
│ ├─→ 是:检查堆栈跟踪
│ │ │
│ │ ├─→ 堆栈显示正在执行你的代码
│ │ │ → 繁忙:主线程在执行任务
│ │ │ → 使用Time Profiler分析
│ │ │
│ │ └─→ 堆栈显示等待状态(信号量、锁、dispatch_sync)
│ │ → 阻塞:主线程在等待
│ │ → 使用System Trace分析
│ │
│ └─→ 否:能否复现问题?
│ │
│ ├─→ 是:优先使用Time Profiler分析
│ │ │
│ │ ├─→ 主线程CPU占用高
│ │ │ → 繁忙:优化任务执行
│ │ │
│ │ └─→ CPU占用低,线程被阻塞
│ │ → 使用System Trace查找阻塞原因
│ │
│ └─→ 否:在应用中启用MetricKit
│ → 等待线上报告
│ → 检查Organizer > 卡顿页面Tool Selection
工具选择
| Scenario | Primary Tool | Why |
|---|---|---|
| Reproduces locally | Time Profiler | See exactly what main thread is doing |
| Blocked thread suspected | System Trace | Shows thread state, lock contention |
| Field reports only | Xcode Organizer | Aggregated hang diagnostics |
| Want in-app data | MetricKit | MXHangDiagnostic with call stacks |
| Need precise timing | System Trace | Nanosecond-level thread analysis |
| 场景 | 首选工具 | 原因 |
|---|---|---|
| 可在本地复现 | Time Profiler | 清晰查看主线程正在执行的任务 |
| 怀疑线程被阻塞 | System Trace | 显示线程状态、锁竞争情况 |
| 仅能获取线上报告 | Xcode Organizer | 聚合的卡顿诊断数据 |
| 需要应用内数据 | MetricKit | 包含调用栈的MXHangDiagnostic数据 |
| 需要精确计时 | System Trace | 纳秒级线程分析 |
Time Profiler Workflow for Hangs
卡顿问题的Time Profiler工作流
- Launch Instruments → Select Time Profiler template
- Record during hang → Reproduce the freeze
- Stop recording → Find the hang period in timeline
- Select hang region → Drag to select frozen timespan
- Examine call tree → Look for main thread work
What to look for:
- Functions with high "Self Time" on main thread
- Unexpectedly deep call stacks
- System calls that shouldn't be on main thread
- 启动Instruments → 选择Time Profiler模板
- 在卡顿期间录制 → 复现冻结场景
- 停止录制 → 在时间轴中找到卡顿时间段
- 选择卡顿区域 → 拖动选中冻结的时间范围
- 检查调用树 → 查找主线程执行的任务
需要关注的点:
- 主线程上“Self Time”占比高的函数
- 异常深的调用栈
- 不应在主线程执行的系统调用
System Trace Workflow for Blocked Hangs
阻塞型卡顿的System Trace工作流
- Launch Instruments → Select System Trace template
- Record during hang → Capture thread states
- Find main thread → Filter to main thread
- Look for red/orange → Blocked states
- Examine blocking reason → Lock, semaphore, IPC
Thread states:
- Running (blue): Executing code
- Preempted (orange): Runnable but not scheduled
- Blocked (red): Waiting for resource
- 启动Instruments → 选择System Trace模板
- 在卡顿期间录制 → 捕获线程状态
- 找到主线程 → 过滤到主线程
- 寻找红/橙色标记 → 阻塞状态
- 检查阻塞原因 → 锁、信号量、IPC
线程状态:
- 运行中(蓝色): 正在执行代码
- 被抢占(橙色): 可运行但未被调度
- 阻塞(红色): 等待资源
Common Hang Patterns and Fixes
常见卡顿模式与修复方案
Pattern 1: Synchronous File I/O
模式1:同步文件I/O
Before (hangs):
swift
// Main thread blocks on file read
func loadUserData() {
let data = try! Data(contentsOf: largeFileURL) // BLOCKS
processData(data)
}After (async):
swift
func loadUserData() {
Task.detached {
let data = try Data(contentsOf: largeFileURL)
await MainActor.run {
self.processData(data)
}
}
}修复前(会卡顿):
swift
// 主线程阻塞在文件读取上
func loadUserData() {
let data = try! Data(contentsOf: largeFileURL) // 阻塞
processData(data)
}修复后(异步):
swift
func loadUserData() {
Task.detached {
let data = try Data(contentsOf: largeFileURL)
await MainActor.run {
self.processData(data)
}
}
}Pattern 2: Unfiltered Notification Observer
模式2:未过滤的通知观察者
Before (processes all):
swift
NotificationCenter.default.addObserver(
self,
selector: #selector(handleChange),
name: .NSManagedObjectContextObjectsDidChange,
object: nil // Receives ALL contexts
)After (filtered):
swift
NotificationCenter.default.addObserver(
self,
selector: #selector(handleChange),
name: .NSManagedObjectContextObjectsDidChange,
object: relevantContext // Only this context
)修复前(处理所有通知):
swift
NotificationCenter.default.addObserver(
self,
selector: #selector(handleChange),
name: .NSManagedObjectContextObjectsDidChange,
object: nil // 接收所有上下文的通知
)修复后(过滤):
swift
NotificationCenter.default.addObserver(
self,
selector: #selector(handleChange),
name: .NSManagedObjectContextObjectsDidChange,
object: relevantContext // 仅接收该上下文的通知
)Pattern 3: Expensive Formatter Creation
模式3:频繁创建昂贵的格式化器
Before (creates each time):
swift
func formatDate(_ date: Date) -> String {
let formatter = DateFormatter() // EXPENSIVE
formatter.dateStyle = .medium
return formatter.string(from: date)
}After (cached):
swift
private static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter
}()
func formatDate(_ date: Date) -> String {
Self.dateFormatter.string(from: date)
}修复前(每次调用都创建):
swift
func formatDate(_ date: Date) -> String {
let formatter = DateFormatter() // 开销大
formatter.dateStyle = .medium
return formatter.string(from: date)
}修复后(缓存复用):
swift
private static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter
}()
func formatDate(_ date: Date) -> String {
Self.dateFormatter.string(from: date)
}Pattern 4: dispatch_sync to Main Thread
模式4:向主线程dispatch_sync
Before (deadlock risk):
swift
// From background thread
DispatchQueue.main.sync { // BLOCKS if main is blocked
updateUI()
}After (async):
swift
DispatchQueue.main.async {
self.updateUI()
}修复前(有死锁风险):
swift
// 在后台线程调用
DispatchQueue.main.sync { // 如果主线程被阻塞,此处会阻塞
updateUI()
}修复后(异步):
swift
DispatchQueue.main.async {
self.updateUI()
}Pattern 5: Semaphore for Async Result
模式5:使用信号量获取异步结果
Before (blocks main thread):
swift
func fetchDataSync() -> Data {
let semaphore = DispatchSemaphore(value: 0)
var result: Data?
URLSession.shared.dataTask(with: url) { data, _, _ in
result = data
semaphore.signal()
}.resume()
semaphore.wait() // BLOCKS MAIN THREAD
return result!
}After (async/await):
swift
func fetchData() async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}修复前(阻塞主线程):
swift
func fetchDataSync() -> Data {
let semaphore = DispatchSemaphore(value: 0)
var result: Data?
URLSession.shared.dataTask(with: url) { data, _, _ in
result = data
semaphore.signal()
}.resume()
semaphore.wait() // 阻塞主线程
return result!
}修复后(async/await):
swift
func fetchData() async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}Pattern 6: Lock Contention
模式6:锁竞争
Before (shared lock):
swift
class DataManager {
private let lock = NSLock()
private var cache: [String: Data] = [:]
func getData(for key: String) -> Data? {
lock.lock() // Main thread waits for background
defer { lock.unlock() }
return cache[key]
}
}After (actor):
swift
actor DataManager {
private var cache: [String: Data] = [:]
func getData(for key: String) -> Data? {
cache[key] // Actor serializes access safely
}
}修复前(共享锁):
swift
class DataManager {
private let lock = NSLock()
private var cache: [String: Data] = [:]
func getData(for key: String) -> Data? {
lock.lock() // 主线程等待后台线程释放锁
defer { lock.unlock() }
return cache[key]
}
}修复后(Actor):
swift
actor DataManager {
private var cache: [String: Data] = [:]
func getData(for key: String) -> Data? {
cache[key] // Actor安全地序列化访问
}
}Pattern 7: App Launch Hang (Watchdog)
模式7:应用启动卡顿(看门狗)
Before (too much work):
swift
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
loadAllUserData() // Expensive
setupAnalytics() // Network calls
precomputeLayouts() // CPU intensive
return true
}After (deferred):
swift
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Only essential setup
setupMinimalUI()
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Defer non-essential work
Task {
await loadUserDataInBackground()
}
}修复前(启动任务过多):
swift
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
loadAllUserData() // 开销大
setupAnalytics() // 网络请求
precomputeLayouts() // CPU密集型任务
return true
}修复后(延迟执行):
swift
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 仅执行必要的初始化
setupMinimalUI()
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
// 延迟执行非必要任务
Task {
await loadUserDataInBackground()
}
}Pattern 8: Image Processing on Main Thread
模式8:主线程处理图片
Before (blocks UI):
swift
func processImage(_ image: UIImage) {
let filtered = applyExpensiveFilter(image) // BLOCKS
imageView.image = filtered
}After (background processing):
swift
func processImage(_ image: UIImage) {
imageView.image = placeholder
Task.detached(priority: .userInitiated) {
let filtered = applyExpensiveFilter(image)
await MainActor.run {
self.imageView.image = filtered
}
}
}修复前(阻塞UI):
swift
func processImage(_ image: UIImage) {
let filtered = applyExpensiveFilter(image) // 阻塞
imageView.image = filtered
}修复后(后台处理):
swift
func processImage(_ image: UIImage) {
imageView.image = placeholder
Task.detached(priority: .userInitiated) {
let filtered = applyExpensiveFilter(image)
await MainActor.run {
self.imageView.image = filtered
}
}
}Xcode Organizer Hang Diagnostics
Xcode Organizer卡顿诊断
Window > Organizer > Select App > Hangs
The Organizer shows aggregated hang data from users who opted into sharing diagnostics.
Reading the report:
- Hang Rate: Hangs per day per device
- Call Stack: Where the hang occurred
- Device/OS breakdown: Which configurations affected
Interpreting call stacks:
- Your code at top: Main thread busy with your work
- System API at top: You called blocking API on main thread
- pthread_mutex/semaphore: Lock contention or explicit waiting
窗口 > Organizer > 选择应用 > 卡顿
Organizer展示了选择共享诊断数据的用户的聚合卡顿数据。
报告解读:
- 卡顿率: 每台设备每天的卡顿次数
- 调用栈: 卡顿发生的位置
- 设备/系统版本分布: 受影响的配置
调用栈解读:
- 栈顶是你的代码: 主线程在执行你的任务
- 栈顶是系统API: 你在主线程调用了阻塞式系统API
- pthread_mutex/semaphore: 存在锁竞争或显式等待
MetricKit Hang Diagnostics
MetricKit卡顿诊断
Adopt MetricKit to receive hang diagnostics in your app:
swift
import MetricKit
class MetricsSubscriber: NSObject, MXMetricManagerSubscriber {
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for payload in payloads {
if let hangDiagnostics = payload.hangDiagnostics {
for diagnostic in hangDiagnostics {
analyzeHang(diagnostic)
}
}
}
}
private func analyzeHang(_ diagnostic: MXHangDiagnostic) {
// Duration of the hang
let duration = diagnostic.hangDuration
// Call stack tree (needs symbolication)
let callStack = diagnostic.callStackTree
// Send to your analytics
uploadHangDiagnostic(duration: duration, callStack: callStack)
}
}Key MXHangDiagnostic properties:
- : How long the hang lasted
hangDuration - : MXCallStackTree with frames
callStackTree - : For grouping similar hangs
signatureIdentifier
在应用中集成MetricKit以接收卡顿诊断数据:
swift
import MetricKit
class MetricsSubscriber: NSObject, MXMetricManagerSubscriber {
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for payload in payloads {
if let hangDiagnostics = payload.hangDiagnostics {
for diagnostic in hangDiagnostics {
analyzeHang(diagnostic)
}
}
}
}
private func analyzeHang(_ diagnostic: MXHangDiagnostic) {
// 卡顿持续时间
let duration = diagnostic.hangDuration
// 调用栈树(需要符号化)
let callStack = diagnostic.callStackTree
// 上传至你的分析平台
uploadHangDiagnostic(duration: duration, callStack: callStack)
}
}MXHangDiagnostic关键属性:
- : 卡顿持续时间
hangDuration - : 包含调用栈帧的MXCallStackTree
callStackTree - : 用于分组相似卡顿
signatureIdentifier
Watchdog Terminations
看门狗终止
The watchdog kills apps that hang during key transitions:
| Transition | Time Limit | Consequence |
|---|---|---|
| App launch | ~20 seconds | App killed, crash logged |
| Background transition | ~5 seconds | App killed |
| Foreground transition | ~10 seconds | App killed |
Watchdog disabled in:
- Simulator
- Debugger attached
- Development builds (sometimes)
Watchdog kills are logged as crashes with exception type and termination reason (hex — indicates app held a file lock or SQLite database lock while being suspended).
EXC_CRASH (SIGKILL)Namespace RUNNINGBOARD, Code 37358839800xDEAD10CC看门狗会在应用在关键过渡阶段发生卡顿时终止应用:
| 过渡阶段 | 时间限制 | 后果 |
|---|---|---|
| 应用启动 | ~20秒 | 应用被终止,记录崩溃日志 |
| 后台切换 | ~5秒 | 应用被终止 |
| 前台切换 | ~10秒 | 应用被终止 |
看门狗在以下场景中禁用:
- 模拟器
- 附加了调试器
- 开发构建版本(有时)
看门狗终止会被记录为崩溃,异常类型为,终止原因为(十六进制——表示应用在被挂起时持有文件锁或SQLite数据库锁)。
EXC_CRASH (SIGKILL)Namespace RUNNINGBOARD, Code 37358839800xDEAD10CCPressure Scenarios
压力场景
Scenario 1: Manager Says "Just Add a Loading Spinner"
场景1:经理说“加个加载动画就行了”
Situation: App hangs during data load. Manager suggests adding spinner to "fix" it.
Why this fails: Adding a spinner doesn't prevent the hang—the UI still freezes, the spinner won't animate, and the app remains unresponsive.
Correct response: "A spinner won't animate during a hang because the main thread is blocked. We need to move this work off the main thread so the spinner can actually spin and the app stays responsive."
情况: 应用在数据加载时卡顿。经理建议添加加载动画来“修复”问题。
为何无效: 添加加载动画无法阻止卡顿——UI仍然会冻结,动画无法播放,应用依然无响应。
正确回应: “卡顿期间主线程被阻塞,加载动画无法播放。我们需要将任务移至后台线程,这样动画才能正常播放,应用也能保持响应。”
Scenario 2: "It Works Fine in Testing"
场景2:“测试环境一切正常”
Situation: QA can't reproduce the hang. Logs show it happens in production.
Analysis:
- Field devices have different data sizes
- Network conditions vary (slow connection = longer sync)
- Background apps consume memory/CPU
- Watchdog is disabled in debug builds
Action:
- Add MetricKit to capture field diagnostics
- Test with production-sized datasets
- Test without debugger attached
- Check Organizer for hang reports
情况: QA无法复现卡顿,但日志显示生产环境中存在该问题。
分析:
- 线上设备的数据量不同
- 网络条件各异(慢速连接会导致同步时间更长)
- 后台应用占用内存/CPU
- 调试构建版本中看门狗被禁用
行动:
- 集成MetricKit以捕获线上诊断数据
- 使用生产级数据集测试
- 在未附加调试器的情况下测试
- 检查Organizer中的卡顿报告
Scenario 3: "We've Always Done It This Way"
场景3:“我们一直都是这么做的”
Situation: Legacy code calls synchronous API on main thread. Refactoring is "too risky."
Why it matters: Even if it worked before:
- Data may have grown larger
- OS updates may have changed timing
- New devices have different characteristics
- Users notice more as apps get faster
Approach:
- Add metrics to measure current hang rate
- Refactor incrementally with feature flags
- A/B test to show improvement
- Document risk of not fixing
情况: 遗留代码在主线程调用同步API。重构被认为“风险太高”。
为何重要: 即使之前能正常工作:
- 数据量可能已增长
- 系统更新可能改变了计时
- 新设备有不同的特性
- 随着应用整体变快,用户对卡顿更敏感
应对方法:
- 添加指标以衡量当前卡顿率
- 使用功能标志逐步重构
- 通过A/B测试展示优化效果
- 记录不修复的风险
Anti-Patterns to Avoid
需避免的反模式
| Anti-Pattern | Why It's Wrong | Instead |
|---|---|---|
| Can deadlock, always blocks | Use |
| Semaphore to convert async to sync | Blocks calling thread | Stay async with completion/await |
| File I/O on main thread | Unpredictable latency | Background queue |
| Unfiltered notification observer | Processes irrelevant events | Filter by object/name |
| Creating formatters in loops | Expensive initialization | Cache and reuse |
| Synchronous network request | Blocks on network latency | URLSession async |
| 反模式 | 问题所在 | 正确做法 |
|---|---|---|
从后台线程调用 | 可能导致死锁,始终会阻塞 | 使用 |
| 使用信号量将异步转换为同步 | 阻塞调用线程 | 保持异步,使用回调或await |
| 主线程执行文件I/O | 延迟不可预测 | 移至后台队列执行 |
| 未过滤的通知观察者 | 处理无关事件 | 按对象/名称过滤通知 |
| 循环中创建格式化器 | 初始化开销大 | 缓存并复用格式化器 |
| 同步网络请求 | 因网络延迟阻塞 | 使用URLSession异步接口 |
Hang Prevention Checklist
卡顿预防检查清单
Before shipping, verify:
- No or file reads on main thread
Data(contentsOf:) - No from background threads
DispatchQueue.main.sync - No semaphore.wait() on main thread
- Formatters (DateFormatter, NumberFormatter) are cached
- Notification observers filter appropriately
- Launch work is minimized (defer non-essential)
- Image processing happens off main thread
- Database queries don't run on main thread
- MetricKit adopted for field diagnostics
发布前,请确认:
- 主线程未调用或执行文件读取
Data(contentsOf:) - 未从后台线程调用
DispatchQueue.main.sync - 主线程未调用semaphore.wait()
- 格式化器(DateFormatter、NumberFormatter)已缓存
- 通知观察者已正确过滤
- 启动任务已最小化(非必要任务延迟执行)
- 图片处理在后台线程执行
- 数据库查询未在主线程运行
- 应用已集成MetricKit以获取线上诊断数据
Resources
资源
WWDC: 2021-10258, 2022-10082
Docs: /xcode/analyzing-responsiveness-issues-in-your-shipping-app, /metrickit/mxhangdiagnostic
Skills: axiom-metrickit-ref, axiom-performance-profiling, axiom-swift-concurrency
WWDC: 2021-10258, 2022-10082
文档: /xcode/analyzing-responsiveness-issues-in-your-shipping-app, /metrickit/mxhangdiagnostic
相关技能: axiom-metrickit-ref, axiom-performance-profiling, axiom-swift-concurrency