axiom-performance-profiling
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePerformance Profiling
性能分析
Overview
概述
iOS app performance problems fall into distinct categories, each with a specific diagnosis tool. This skill helps you choose the right tool, use it effectively, and interpret results correctly under pressure.
Core principle: Measure before optimizing. Guessing about performance wastes more time than profiling.
Requires: Xcode 15+, iOS 14+
Related skills: (SwiftUI-specific profiling with Instruments 26), (memory leak diagnosis)
axiom-swiftui-performanceaxiom-memory-debuggingiOS应用的性能问题分为不同类别,每个类别都有对应的诊断工具。本技能帮助你选择合适的工具、有效使用工具并正确解读结果。
核心原则:优化前先测量。猜测性能问题比实际分析更浪费时间。
要求:Xcode 15+、iOS 14+
相关技能:(基于Instruments 26的SwiftUI专属性能分析)、(内存泄漏诊断)
axiom-swiftui-performanceaxiom-memory-debuggingWhen to Use Performance Profiling
何时使用性能分析
Use this skill when
以下情况使用本技能
- ✅ App feels slow (UI lags, loads take 5+ seconds)
- ✅ Memory grows over time (Xcode shows increasing memory usage)
- ✅ Battery drains fast (device gets hot, battery depletes in hours)
- ✅ You want to profile proactively (before users complain)
- ✅ You're unsure which Instruments tool to use
- ✅ Profiling results are confusing or contradictory
- ✅ 应用运行缓慢(UI卡顿、加载耗时超过5秒)
- ✅ 内存持续增长(Xcode显示内存使用量不断上升)
- ✅ 电池消耗过快(设备发热,数小时内电量耗尽)
- ✅ 想要主动进行性能分析(在用户投诉前排查问题)
- ✅ 不确定应使用哪款Instruments工具
- ✅ 性能分析结果令人困惑或自相矛盾
Use axiom-memory-debugging
instead when
axiom-memory-debugging以下情况改用axiom-memory-debugging
axiom-memory-debugging- Investigating specific memory leaks with retain cycles
- Using Instruments Allocations in detail mode
- 调查由引用循环导致的特定内存泄漏
- 详细使用Instruments的Allocations工具
Use axiom-swiftui-performance
instead when
axiom-swiftui-performance以下情况改用axiom-swiftui-performance
axiom-swiftui-performance- Analyzing SwiftUI view body updates
- Using SwiftUI Instrument specifically
- 分析SwiftUI视图体的更新情况
- 专门使用SwiftUI Instrument工具
Performance Decision Tree
性能分析决策树
Before opening Instruments, narrow down what you're actually investigating.
打开Instruments之前,先缩小你要调查的问题范围。
Step 1: What's the Symptom?
步骤1:症状是什么?
App performance problem?
├─ App feels slow or lags (UI interactions stall, scrolling stutters)
│ └─ → Use Time Profiler (measure CPU usage)
├─ Memory grows over time (Xcode shows increasing memory)
│ └─ → Use Allocations (measure object creation)
├─ Data loading is slow (parsing, database queries, API calls)
│ └─ → Use Core Data instrument (if using Core Data)
│ └─ → Use Time Profiler (if it's computation)
└─ Battery drains fast (device gets hot, depletes in hours)
└─ → Use Energy Impact (measure power consumption)存在应用性能问题?
├─ 应用运行缓慢或卡顿(UI交互停滞、滚动不流畅)
│ └─ → 使用Time Profiler(测量CPU使用率)
├─ 内存持续增长(Xcode显示内存使用量上升)
│ └─ → 使用Allocations(测量对象创建情况)
├─ 数据加载缓慢(解析、数据库查询、API调用耗时久)
│ └─ → 使用Core Data工具(如果使用了Core Data)
│ └─ → 使用Time Profiler(如果是计算耗时)
└─ 电池消耗过快(设备发热,数小时内电量耗尽)
└─ → 使用Energy Impact(测量功耗)Step 2: Can You Reproduce It?
步骤2:能否复现问题?
YES – Use Instruments to measure it (profiling is most accurate)
NO – Use profiling proactively
- Enable Core Data SQL debugging to catch N+1 queries
- Profile app during normal use (scrolling, loading, navigation)
- Establish baseline metrics before changes
可以 – 使用Instruments进行测量(分析结果最准确)
不可以 – 主动进行性能分析
- 启用Core Data SQL调试以捕获N+1查询问题
- 在正常使用场景下分析应用(滚动、加载、导航)
- 在进行更改前建立基准指标
Step 3: Which Instruments Tool?
步骤3:选择哪款Instruments工具?
Time Profiler – Slowness, UI lag, CPU spikes
Allocations – Memory growth, memory pressure, object counts
Core Data – Query performance, fetch times, fault fires
Energy Impact – Battery drain, sustained power draw
Network Link Conditioner – Connection-related slowness
System Trace – Thread blocking, main thread blocking, scheduling
Time Profiler – 应用卡顿、UI延迟、CPU峰值
Allocations – 内存增长、内存压力、对象数量
Core Data – 查询性能、获取耗时、错误触发
Energy Impact – 电池消耗、持续功耗
Network Link Conditioner – 网络相关的运行缓慢问题
System Trace – 线程阻塞、主线程阻塞、调度问题
Time Profiler Deep Dive
Time Profiler 深度解析
Use Time Profiler when your app feels slow or laggy. It measures CPU time spent in each function.
当应用运行缓慢或卡顿时,使用Time Profiler。它会测量每个函数的CPU耗时。
Workflow: Record and Analyze
工作流:录制与分析
Step 1: Launch Instruments
步骤1:启动Instruments
bash
open -a InstrumentsSelect "Time Profiler" template.
bash
open -a Instruments选择「Time Profiler」模板。
Step 2: Attach to Running App
步骤2:附加到运行中的应用
- Start your app in simulator or device
- In Instruments, select your app from the target dropdown
- Click Record (red circle)
- Interact with the slow part (scroll, tap buttons, load data)
- Stop recording after 10-30 seconds of interaction
- 在模拟器或设备上启动你的应用
- 在Instruments中,从目标下拉菜单选择你的应用
- 点击录制(红色圆圈)
- 与应用的缓慢部分进行交互(滚动、点击按钮、加载数据)
- 交互10-30秒后停止录制
Step 3: Read the Call Stack
步骤3:读取调用栈
The top panel shows a timeline of CPU usage over time. Look for:
- Tall spikes – Brief CPU-intensive operations
- Sustained high usage – Continuous expensive work
- Main thread blocking – UI thread doing work (causes UI lag)
顶部面板显示CPU使用率随时间变化的时间线。需要关注:
- 高尖峰 – 短暂的CPU密集型操作
- 持续高使用率 – 持续的高负载工作
- 主线程阻塞 – UI线程在执行任务(会导致UI卡顿)
Step 4: Drill Down to Hot Spots
步骤4:深入定位热点
In the call tree, click "Heaviest Stack Trace" to see which functions use the most CPU:
Time Profiler Results
MyViewController.viewDidLoad() – 500ms (40% of total)
├─ DataParser.parse() – 350ms
│ └─ JSONDecoder.decode() – 320ms
└─ UITableView.reloadData() – 150msSelf Time = Time spent IN that function (not in functions it calls)
Total Time = Time spent in that function + everything it calls
在调用树中,点击「Heaviest Stack Trace」查看占用CPU最多的函数:
Time Profiler 结果
MyViewController.viewDidLoad() – 500ms(占总耗时的40%)
├─ DataParser.parse() – 350ms
│ └─ JSONDecoder.decode() – 320ms
└─ UITableView.reloadData() – 150msSelf Time = 该函数本身的耗时(不包含它调用的函数)
Total Time = 该函数本身的耗时 + 它调用的所有函数的耗时
Common Mistakes & Fixes
常见错误与修复
❌ Mistake 1: Blaming the Wrong Function
❌ 错误1:指责错误的函数
swift
// ❌ WRONG: Profile shows DataParser.parse() is 80% CPU
// Conclusion: "DataParser is slow, let me optimize it"
// ✅ RIGHT: Check what DataParser is calling
// If JSONDecoder.decode() is doing 99% of the work,
// optimize JSON decoding, not DataParserThe issue: A function with high Total Time might be calling slow code, not doing slow work itself.
Fix: Look at Self Time, not Total Time. Drill down to see what each function calls.
swift
// ❌ 错误:性能分析显示DataParser.parse()占用80% CPU
// 结论:「DataParser运行缓慢,我要优化它」
// ✅ 正确:检查DataParser调用的内容
// 如果JSONDecoder.decode()占用了99%的耗时,
// 应该优化JSON解码,而非DataParser问题:Total Time高的函数可能只是调用了缓慢的代码,而非自身执行耗时操作。
修复:查看Self Time而非Total Time。深入查看每个函数调用的内容。
❌ Mistake 2: Profiling the Wrong Code Path
❌ 错误2:在错误的环境中分析
swift
// ❌ WRONG: Profile app in Simulator
// Simulator CPU is different than real device
// Results don't reflect actual device performance
// ✅ RIGHT: Profile on actual device
// Device settings: Developer Mode enabled, Xcode attachedFix: Always profile on actual device for accurate CPU measurements.
swift
// ❌ 错误:在模拟器中分析应用
// 模拟器的CPU与真实设备不同
// 结果无法反映实际设备的性能
// ✅ 正确:在真实设备上分析
// 设备设置:启用开发者模式,连接Xcode修复:始终在真实设备上进行分析以获取准确的CPU测量结果。
❌ Mistake 3: Not Isolating the Problem
❌ 错误3:未隔离问题
swift
// ❌ WRONG: Profile entire app startup
// Sees 2000ms startup time, many functions involved
// ✅ RIGHT: Profile just the slow part
// "App feels slow when scrolling" → profile only scrolling
// Separate concerns: startup slow vs interaction slowFix: Reproduce the specific slow operation, not the entire app.
swift
// ❌ 错误:分析整个应用启动过程
// 看到启动耗时2000ms,涉及多个函数
// ✅ 正确:仅分析缓慢的部分
// 「应用滚动时卡顿」→ 仅分析滚动操作
// 区分不同场景:启动缓慢 vs 交互缓慢修复:复现特定的缓慢操作,而非分析整个应用。
Pressure Scenario: "Profile Shows Function X is 80% CPU"
压力场景:「性能分析显示函数X占用80% CPU」
The temptation: "I must optimize function X!"
The reality: Function X might be:
- Calling expensive code (optimize the called function, not X)
- Running on main thread (move to background, it's already optimized)
- Necessary work that looks slow (baseline is acceptable, user won't notice)
What to do instead:
-
Check Self Time, not Total Time
- Self Time 80%? Function is actually doing expensive work
- Self Time 5%, Total Time 80%? Function is calling slow code
-
Drill down one level
- What is this function calling?
- Is the slow code in a library you control?
-
Check the timeline
- Is this 80% sustained (steady slow) or spikes (occasional stalls)?
- Sustained = optimization needed
- Spikes = caching might help
-
Ask: Will users notice?
- 500ms background work = user won't notice
- 500ms on main thread = UI stall, user sees it
- 50ms on main thread per frame = smooth UI (60fps)
Time cost: 5 min (read results) + 2 min (drill down) = 7 minutes to understand
Cost of guessing: 2 hours optimizing wrong function + 1 hour realizing it didn't help + back to square one = 3+ hours wasted
直觉反应:「我必须优化函数X!」
实际情况:函数X可能:
- 调用了高耗时代码(优化被调用的函数,而非X)
- 在主线程运行(移至后台执行,它本身已经是优化过的)
- 是必要的耗时操作(基准耗时可接受,用户不会注意到)
正确做法:
-
查看Self Time而非Total Time
- Self Time占80%?函数确实在执行高耗时操作
- Self Time占5%,Total Time占80%?函数调用了缓慢的代码
-
深入一层查看
- 这个函数调用了什么?
- 缓慢的代码是否在你可控的库中?
-
查看时间线
- 这80%的CPU占用是持续的(一直缓慢)还是尖峰(偶尔停滞)?
- 持续占用 = 需要优化
- 尖峰 = 缓存可能会有帮助
-
思考:用户会注意到吗?
- 后台操作耗时500ms = 用户不会注意到
- 主线程操作耗时500ms = UI停滞,用户会看到
- 每帧主线程耗时50ms = UI流畅(60fps)
时间成本:5分钟(读取结果)+ 2分钟(深入分析)= 7分钟即可理解问题
猜测的成本:2小时优化错误的函数 + 1小时意识到没有效果 + 回到原点 = 浪费3小时以上
Allocations Deep Dive
Allocations 深度解析
Use Allocations when memory grows over time or you suspect memory pressure issues.
当内存持续增长或你怀疑存在内存压力问题时,使用Allocations。
Workflow: Record and Analyze
工作流:录制与分析
Step 1: Launch Instruments
步骤1:启动Instruments
bash
open -a InstrumentsSelect "Allocations" template.
bash
open -a Instruments选择「Allocations」模板。
Step 2: Attach and Record
步骤2:附加并录制
- Start your app
- In Instruments, select your app
- Click Record
- Perform actions that use memory (load data, display images, navigate)
- Stop recording after memory stabilizes or peaks
- 启动你的应用
- 在Instruments中选择你的应用
- 点击录制
- 执行消耗内存的操作(加载数据、显示图片、导航)
- 内存稳定或达到峰值后停止录制
Step 3: Find Memory Growth
步骤3:发现内存增长
Look at the main chart:
- Blue line = Total allocations
- Sharp climb = Memory being allocated
- Flat line = Memory stable (good)
- No decline after stopping actions = Possible leak (or caching)
查看主图表:
- 蓝线 = 总分配内存
- 急剧上升 = 内存正在被分配
- 平稳线条 = 内存稳定(良好)
- 停止操作后没有下降 = 可能存在泄漏(或缓存)
Step 4: Identify Persistent Objects
步骤4:识别持久对象
Under "Statistics":
- Sort by "Persistent" (objects still alive)
- Look for surprisingly large object counts:
UIImage: 500 instances (300MB) – Should be <50 for normal app NSString: 50000 instances – Should be <1000 CustomDataModel: 10000 instances – Should be <100
在「Statistics」下:
- 按「Persistent」排序(仍在存活的对象)
- 寻找数量异常多的对象:
UIImage: 500个实例(300MB)– 正常应用应少于50个 NSString: 50000个实例 – 正常应少于1000个 CustomDataModel: 10000个实例 – 正常应少于100个
Common Mistakes & Fixes
常见错误与修复
❌ Mistake 1: Confusing "Memory Grew" with "Memory Leak"
❌ 错误1:将「内存增长」误认为「内存泄漏」
swift
// ❌ WRONG: Memory went from 100MB to 500MB
// Conclusion: "There's a leak, memory keeps growing!"
// ✅ RIGHT: Check what caused the growth
// Loaded 1000 images (normal)
// Cached API responses (normal)
// User has 5000 contacts (normal)
// Memory is being used correctlyThe issue: Growing memory ≠ leak. Apps legitimately use more memory when loading data.
Fix: Check Allocations for object counts. If images/data count matches what you loaded, it's normal. If object count keeps growing without actions, that's a leak.
swift
// ❌ 错误:内存从100MB增长到500MB
// 结论:「存在泄漏,内存一直在增长!」
// ✅ 正确:检查增长的原因
// 加载了1000张图片(正常)
// 缓存了API响应(正常)
// 用户有5000个联系人(正常)
// 内存被合理使用问题:内存增长 ≠ 泄漏。应用在加载数据时会合理使用更多内存。
修复:查看Allocations中的对象数量。如果图片/数据数量与你加载的内容匹配,那是正常的。如果对象数量在没有操作的情况下持续增长,那才是泄漏。
❌ Mistake 2: Not Accounting for Caching
❌ 错误2:未考虑缓存
swift
// ❌ WRONG: Allocations shows 1000 UIImages in memory
// Conclusion: "Memory leak, too many images!"
// ✅ RIGHT: Check if this is intentional caching
// ImageCache holds up to 1000 images by design
// When memory pressure happens, cache is cleared
// Normal behaviorFix: Distinguish between intended caching and actual leaks. Leaks don't release under memory pressure.
swift
// ❌ 错误:Allocations显示内存中有1000个UIImage
// 结论:「内存泄漏,图片太多了!」
// ✅ 正确:检查这是否是有意的缓存
// ImageCache设计为最多存储1000张图片
// 当内存压力出现时,缓存会被清除
// 这是正常行为修复:区分有意缓存和实际泄漏。泄漏的内存不会在内存压力下被释放。
❌ Mistake 3: Profiling Too Short
❌ 错误3:分析时间过短
swift
// ❌ WRONG: Record for 5 seconds, see 200MB
// Conclusion: "App uses 200MB, optimize memory"
// ✅ RIGHT: Record for 2-3 minutes, see full lifecycle
// Load data: 200MB
// Navigate away: 180MB (20MB still cached)
// Navigate back: 190MB (cache reused)
// Real baseline: ~190MB at steady stateFix: Profile long enough to see memory stabilize. Short recordings capture transient spikes.
swift
// ❌ 错误:录制5秒,看到内存200MB
// 结论:「应用占用200MB,需要优化内存」
// ✅ 正确:录制2-3分钟,查看完整生命周期
// 加载数据:200MB
// 导航离开:180MB(20MB仍在缓存中)
// 返回应用:190MB(复用缓存)
// 真实基准:稳定状态下约190MB修复:录制足够长的时间以查看内存稳定情况。短时间录制只能捕获临时的尖峰。
Pressure Scenario: "Memory is 500MB, That's a Leak!"
压力场景:「内存占用500MB,这是泄漏!」
The temptation: "Delete caching, reduce object creation, optimize data structures"
The reality: Is 500MB actually large?
- iPhone 14 Pro has 6GB RAM
- Instagram uses 400-600MB on load
- Photos app uses 500MB+ when browsing large library
- 500MB might be completely normal
What to do instead:
-
Establish baseline on real devicebash
# On device, open Memory view in Xcode Xcode → Debug → Memory Debugger → Check "Real Memory" at app launch -
Check object counts, not total memory
- Allocations → Statistics → "Persistent"
- Are images, views, or data objects 10x expected count?
- If yes, investigate that object type
- If no, memory is probably fine
-
Test under memory pressure
- Xcode → Debug → Simulate Memory Warning
- Does memory drop by 50%+? It's caching (normal)
- Does memory stay high? Investigate persistent objects
-
Profile real user journey
- Load data (like user does)
- Navigate around (like user does)
- Return to app (from background)
- Check memory at each step
Time cost: 5 min (launch Allocations) + 3 min (record app usage) + 2 min (analyze) = 10 minutes
Cost of guessing: Delete caching to "reduce memory" → app reloads data every screen → slower app → users complain → revert changes = 2+ hours wasted
直觉反应:「删除缓存,减少对象创建,优化数据结构」
实际情况:500MB真的算大吗?
- iPhone 14 Pro拥有6GB RAM
- Instagram加载时占用400-600MB
- 照片应用浏览大型图库时占用500MB以上
- 500MB可能完全正常
正确做法:
-
在真实设备上建立基准bash
# 在设备上,打开Xcode的Memory视图 Xcode → Debug → Memory Debugger → 检查应用启动时的「Real Memory」 -
查看对象数量而非总内存
- Allocations → Statistics → 「Persistent」
- 图片、视图或数据对象的数量是否是预期的10倍?
- 如果是,调查该对象类型
- 如果不是,内存可能没问题
-
在内存压力下测试
- Xcode → Debug → Simulate Memory Warning
- 内存是否下降50%以上?这是缓存(正常)
- 内存是否保持高位?调查持久对象
-
分析真实用户的使用流程
- 加载数据(像用户那样)
- 导航浏览(像用户那样)
- 从后台返回应用
- 检查每个步骤的内存情况
时间成本:5分钟(启动Allocations)+ 3分钟(录制应用使用情况)+ 2分钟(分析)= 10分钟
猜测的成本:删除缓存以「减少内存」→ 应用每次切换页面都要重新加载数据 → 应用变慢 → 用户投诉 → 恢复更改 = 浪费2小时以上
Core Data Deep Dive
Core Data 深度解析
Use Core Data instrument when your app uses Core Data and data loading is slow.
当你的应用使用Core Data且数据加载缓慢时,使用Core Data工具。
Workflow: Enable SQL Debugging and Profile
工作流:启用SQL调试并分析
Step 1: Enable Core Data SQL Logging
步骤1:启用Core Data SQL日志
Add to your launch arguments in Xcode:
Edit Scheme → Run → Arguments Passed On Launch
Add: -com.apple.CoreData.SQLDebug 1Now SQLite queries print to console:
CoreData: sql: SELECT ... FROM tracks WHERE artist = ? (time: 0.015s)
CoreData: sql: SELECT ... FROM albums WHERE id = ? (time: 0.002s)在Xcode中添加启动参数:
Edit Scheme → Run → Arguments Passed On Launch
添加:-com.apple.CoreData.SQLDebug 1现在SQLite查询会打印到控制台:
CoreData: sql: SELECT ... FROM tracks WHERE artist = ? (time: 0.015s)
CoreData: sql: SELECT ... FROM albums WHERE id = ? (time: 0.002s)Step 2: Identify N+1 Query Problem
步骤2:识别N+1查询问题
Watch the console during a typical user action (load list, scroll, filter):
❌ BAD: Loading 100 tracks, then querying album for each
SELECT * FROM tracks (time: 0.050s) → 100 tracks
SELECT * FROM albums WHERE id = 1 (time: 0.005s)
SELECT * FROM albums WHERE id = 2 (time: 0.005s)
SELECT * FROM albums WHERE id = 3 (time: 0.005s)
... 97 more queries
Total: 0.050s + (100 × 0.005s) = 0.550s
✅ GOOD: Fetch tracks WITH album relationship (eager loading)
SELECT tracks.*, albums.* FROM tracks
LEFT JOIN albums ON tracks.albumId = albums.id
(time: 0.050s)
Total: 0.050s在典型用户操作期间(加载列表、滚动、筛选)观察控制台:
❌ 错误:加载100首曲目,然后为每首查询专辑
SELECT * FROM tracks (time: 0.050s) → 100首曲目
SELECT * FROM albums WHERE id = 1 (time: 0.005s)
SELECT * FROM albums WHERE id = 2 (time: 0.005s)
SELECT * FROM albums WHERE id = 3 (time: 0.005s)
... 还有97次查询
总耗时:0.050s + (100 × 0.005s) = 0.550s
✅ 正确:获取曲目时同时获取专辑关联(预加载)
SELECT tracks.*, albums.* FROM tracks
LEFT JOIN albums ON tracks.albumId = albums.id
(time: 0.050s)
总耗时:0.050sStep 3: Profile with Core Data Instrument
步骤3:使用Core Data工具分析
bash
open -a InstrumentsSelect "Core Data" template.
Record while performing slow action:
Core Data Results
Fetch Requests: 102
Average Fetch Time: 12ms
Slow Fetch: "SELECT * FROM tracks" (180ms)
Fault Fires: 5000
→ Object accessed, requires fetch from database
→ Should use prefetchingbash
open -a Instruments选择「Core Data」模板。
在执行缓慢操作时录制:
Core Data 结果
Fetch Requests: 102
Average Fetch Time: 12ms
Slow Fetch: "SELECT * FROM tracks" (180ms)
Fault Fires: 5000
→ 对象被访问,需要从数据库获取
→ 应该使用预加载Common Mistakes & Fixes
常见错误与修复
❌ Mistake 1: Not Using Relationships Correctly
❌ 错误1:未正确使用关联关系
swift
// ❌ WRONG: Fetch tracks, then access album for each
let tracks = try context.fetch(Track.fetchRequest())
for track in tracks {
print(track.album.title) // Fires individual query for each
}
// Total: 1 + N queries
// ✅ RIGHT: Fetch with relationship prefetching
let request = Track.fetchRequest()
request.returnsObjectsAsFaults = false
request.relationshipKeyPathsForPrefetching = ["album"]
let tracks = try context.fetch(request)
for track in tracks {
print(track.album.title) // Already loaded
}
// Total: 1 queryFix: Use to load related objects upfront.
relationshipKeyPathsForPrefetchingswift
// ❌ 错误:获取曲目,然后为每首访问专辑
let tracks = try context.fetch(Track.fetchRequest())
for track in tracks {
print(track.album.title) // 为每首曲目触发单独查询
}
// 总查询数:1 + N
// ✅ 正确:获取时预加载关联关系
let request = Track.fetchRequest()
request.returnsObjectsAsFaults = false
request.relationshipKeyPathsForPrefetching = ["album"]
let tracks = try context.fetch(request)
for track in tracks {
print(track.album.title) // 已提前加载
}
// 总查询数:1修复:使用提前加载关联对象。
relationshipKeyPathsForPrefetching❌ Mistake 2: Not Using Batching
❌ 错误2:未使用批处理
swift
// ❌ WRONG: Fetch 50,000 records all at once
let request = Track.fetchRequest()
let allTracks = try context.fetch(request) // Huge memory spike
// ✅ RIGHT: Batch fetch in chunks
let request = Track.fetchRequest()
request.fetchBatchSize = 500 // Fetch 500 at a time
let allTracks = try context.fetch(request) // Memory efficientFix: Use for large datasets.
fetchBatchSizeswift
// ❌ 错误:一次性获取50000条记录
let request = Track.fetchRequest()
let allTracks = try context.fetch(request) // 内存急剧上升
// ✅ 正确:分批获取
let request = Track.fetchRequest()
request.fetchBatchSize = 500 // 每次获取500条
let allTracks = try context.fetch(request) // 内存使用高效修复:对大型数据集使用。
fetchBatchSize❌ Mistake 3: Not Using Faulting to Reduce Memory
❌ 错误3:未使用错误机制减少内存
swift
// ❌ WRONG: Keep all objects in memory
let request = Track.fetchRequest()
request.returnsObjectsAsFaults = false // Keep all in memory
let allTracks = try context.fetch(request) // 50,000 objects
// Memory spike if you don't use all of them
// ✅ RIGHT: Use faults (lazy loading)
let request = Track.fetchRequest()
// request.returnsObjectsAsFaults = true (default)
let allTracks = try context.fetch(request) // Just references
// Only load objects you actually accessFix: Leave as default (true) unless you need all objects upfront.
returnsObjectsAsFaultsswift
// ❌ 错误:将所有对象保存在内存中
let request = Track.fetchRequest()
request.returnsObjectsAsFaults = false // 将所有对象保存在内存中
let allTracks = try context.fetch(request) // 50000个对象
// 如果不使用所有对象,会导致内存尖峰
// ✅ 正确:使用错误机制(懒加载)
let request = Track.fetchRequest()
// request.returnsObjectsAsFaults = true(默认值)
let allTracks = try context.fetch(request) // 仅保存引用
// 仅加载实际访问的对象修复:除非需要提前获取所有对象,否则保持为默认值(true)。
returnsObjectsAsFaultsPressure Scenario: "Core Data Queries Are Slow, Redesign Schema!"
压力场景:「Core Data查询缓慢,重新设计架构!」
The temptation: "The schema is wrong, I need to restructure everything"
The reality: 99% of "slow Core Data" is due to:
- ❌ Missing indexes
- ❌ N+1 query problem
- ❌ Fetching too much data at once
- ❌ Not using batch size or prefetching
Redesigning the schema is the LAST thing to try.
What to do instead:
-
Enable SQL debugging (2 min)
- Add launch argument
-com.apple.CoreData.SQLDebug 1 - Watch what queries execute
- Add
-
Look for N+1 pattern (3 min)
- Fetching 100 objects, then individual queries for related data?
- Add relationship prefetching
-
Add indexes if needed (5 min)
- with frequent filtering?
@NSManaged var artist: String - Add in schema
@Index
-
Test improvement (2 min)
- Re-run the same action
- Compare query count and total time
- If 10x faster, you're done
- If still slow, go to step 5
-
Only THEN consider schema changes (30+ min)
- But you probably won't get here
Time cost: 12 minutes to diagnose + fix = 12 minutes
Cost of schema redesign: 8 hours design + 4 hours migration + 2 hours testing + 1 hour rollback = 15 hours total
直觉反应:「架构有问题,我需要重新构建一切」
实际情况:99%的「Core Data缓慢」问题是由于:
- ❌ 缺少索引
- ❌ N+1查询问题
- ❌ 一次性获取过多数据
- ❌ 未使用批处理大小或预加载
重新设计架构是最后才需要考虑的事情。
正确做法:
-
启用SQL调试(2分钟)
- 添加启动参数
-com.apple.CoreData.SQLDebug 1 - 观察执行的查询
- 添加
-
查找N+1模式(3分钟)
- 获取100个对象,然后为关联数据执行单独查询?
- 添加关联预加载
-
必要时添加索引(5分钟)
- 经常被过滤?
@NSManaged var artist: String - 在架构中添加
@Index
-
测试改进效果(2分钟)
- 重新执行相同操作
- 比较查询数量和总耗时
- 如果速度提升10倍,任务完成
- 如果仍然缓慢,进入步骤5
-
只有这时才考虑架构变更(30分钟以上)
- 但你可能根本不需要走到这一步
时间成本:12分钟诊断+修复 = 12分钟
架构重构的成本:8小时设计 + 4小时迁移 + 2小时测试 + 1小时回滚 = 总计15小时
Quick Reference: Other Tools
快速参考:其他工具
Energy Impact (Battery Drain)
Energy Impact(电池消耗)
When to use: App drains battery fast, device gets hot
Workflow:
- Launch Instruments → Energy Impact template
- Run app normally for 5+ minutes
- Look for red/orange sustained usage (bad)
- Drill down to see which subsystems drain battery
Key metrics:
- Sustained Power – Ongoing energy use (should be minimal)
- Peaks – Brief high usage (acceptable)
- CPU – Process CPU time
- GPU – Graphics rendering
- Network – Cellular/WiFi radio
- Location – GPS usage
Common issues:
- Continuous location updates with 1m accuracy (should be 100m)
- Running timers that wake the device repeatedly
- Excessive network calls (batch requests instead)
- Animating views while not visible
使用场景:应用电池消耗过快,设备发热
工作流:
- 启动Instruments → 选择Energy Impact模板
- 正常运行应用5分钟以上
- 寻找持续的红色/橙色使用情况(不良)
- 深入查看哪些子系统消耗电池
关键指标:
- Sustained Power – 持续能耗(应尽可能低)
- Peaks – 短暂的高能耗(可接受)
- CPU – 进程CPU耗时
- GPU – 图形渲染
- Network – 蜂窝/WiFi无线模块
- Location – GPS使用
常见问题:
- 持续使用1米精度的位置更新(应使用100米精度)
- 运行会反复唤醒设备的计时器
- 过多的网络调用(应批量请求)
- 在不可见时仍在动画视图
Network Link Conditioner (Connection Simulation)
Network Link Conditioner(网络模拟)
When to use: App seems slow on 4G, want to test without traveling
Setup:
- Download Additional Tools for Xcode
- Install Network Link Conditioner
- Open System Preferences → Network Link Conditioner
- Choose profile (3G, LTE, WiFi Slow, etc.)
- Enable and activate profile
- Run app to test
Key profiles:
- 3G – 1.6Mbps down, 768Kbps up, 150ms latency
- LTE – 10Mbps down, 5Mbps up, 20ms latency
- WiFi Slow – 10Mbps, 100ms latency
- Custom – Set your own parameters
Note: Also covered in ui-testing for network-dependent test scenarios.
使用场景:应用在4G网络下运行缓慢,想要在不外出的情况下测试
设置:
- 下载Additional Tools for Xcode
- 安装Network Link Conditioner
- 打开系统偏好设置 → Network Link Conditioner
- 选择配置文件(3G、LTE、WiFi Slow等)
- 启用并激活配置文件
- 运行应用进行测试
关键配置文件:
- 3G – 下行1.6Mbps,上行768Kbps,延迟150ms
- LTE – 下行10Mbps,上行5Mbps,延迟20ms
- WiFi Slow – 10Mbps,延迟100ms
- Custom – 自定义参数
注意:在UI测试中也会涉及网络相关的测试场景。
System Trace (Thread Blocking, Scheduling)
System Trace(线程阻塞、调度)
When to use: UI freezes or is janky, but Time Profiler shows low CPU
Common cause: Main thread blocked by background task waiting on lock
Workflow:
- Launch Instruments → System Trace template
- Record while reproducing issue
- Look for main thread gaps (blocked, not running)
- Drill down to see what's blocking it
Key metrics:
- Main thread gaps – Empty spaces = main thread idle/blocked
- Core scheduling – Which threads run when
- Lock contention – Threads waiting for locks
使用场景:UI冻结或不流畅,但Time Profiler显示CPU使用率低
常见原因:主线程被等待锁的后台任务阻塞
工作流:
- 启动Instruments → 选择System Trace模板
- 在复现问题时录制
- 寻找主线程间隙(被阻塞,未运行)
- 深入查看是什么在阻塞主线程
关键指标:
- Main thread gaps – 空白区域 = 主线程空闲/被阻塞
- Core scheduling – 线程的运行时间
- Lock contention – 等待锁的线程
Pressure Scenarios
压力场景
Scenario 1: "Profiling Shows Different Results Each Run"
场景1:「每次性能分析结果都不同」
The problem: You run Time Profiler 3 times, get 200ms, 150ms, 280ms. Which is correct?
Red flags you might think:
- "Results are unreliable, profiling isn't accurate"
- "Let me just average them"
- "This is too variable, I can't optimize"
The reality: Variance is NORMAL. Different runs hit different:
- Cache states (cold cache = slower)
- System load (other apps running)
- CPU frequency (boost/throttle)
What to do instead:
-
Warm up the cache (first run always slower)
- Perform the action once (cold cache)
- Perform again (warm cache) – use this measurement
-
Control system load
- Close other apps
- Don't touch device during profiling
- Profile on device (not simulator)
-
Look for the pattern
- Multiple runs: 150ms, 160ms, 155ms (consistent = good)
- Multiple runs: 150ms, 280ms, 240ms (inconsistent = investigate)
- Inconsistency = intermittent problem, find it
-
Trust the slowest run (worst case scenario)
- If range is 150-280ms, assume 280ms is real
- Optimize for worst case
Time cost: 10 min (run profiler 3x) + 2 min (interpret) = 12 minutes
Cost of ignoring variance: Miss intermittent performance issue → users see occasional freezes → bad reviews
问题:你运行Time Profiler 3次,得到200ms、150ms、280ms的结果。哪个是正确的?
你可能会想到的错误结论:
- 「结果不可靠,性能分析不准确」
- 「我取平均值吧」
- 「结果太不稳定,我无法优化」
实际情况:差异是正常的。不同的运行会遇到不同的:
- 缓存状态(冷缓存 = 更慢)
- 系统负载(其他应用在运行)
- CPU频率(睿频/降频)
正确做法:
-
预热缓存(第一次运行总是更慢)
- 执行一次操作(冷缓存)
- 再次执行(热缓存)– 使用这次的测量结果
-
控制系统负载
- 关闭其他应用
- 分析期间不要触摸设备
- 在设备上分析(而非模拟器)
-
寻找模式
- 多次运行:150ms、160ms、155ms(稳定 = 良好)
- 多次运行:150ms、280ms、240ms(不稳定 = 需要调查)
- 不稳定 = 间歇性问题,找到它
-
相信最慢的运行结果(最坏情况)
- 如果范围是150-280ms,假设280ms是真实情况
- 针对最坏情况进行优化
时间成本:10分钟(运行3次分析)+ 2分钟(解读)= 12分钟
忽略差异的成本:遗漏间歇性性能问题 → 用户偶尔看到冻结 → 差评
Scenario 2: "Time Profiler and Allocations Show Different Problems"
场景2:「Time Profiler和Allocations显示不同的问题」
The problem: Time Profiler shows JSON parsing is slow. Allocations show memory use is normal. Which to fix?
The answer: Both are real, prioritize differently.
Time Profiler: JSONDecoder.decode() = 500ms
Allocations: Memory = 250MB (normal for app size)
Result: App is slow AND memory is fine
Action: Optimize JSON decoding (not memory)Common conflicts:
| Time Profiler | Allocations | Action |
|---|---|---|
| High CPU | Normal memory | Optimize computation (reduce CPU) |
| Low CPU | Memory growing | Find leak or reduce object creation |
| Both high | Both high | Profile which is user-visible first |
What to do:
-
Prioritize by user impact
- Slowness (UI lag) = fix first
- Memory (background issue) = fix second
-
Check if they're related
- Does JSON parsing leak memory? (No → separate issues)
- Does memory growth slow CPU? (Maybe → fix memory first)
-
Fix in order of impact
- Slow JSON parsing: Affects every data load
- Normal memory: No user impact
- → Fix JSON parsing
Time cost: 5 min (analyze both results) = 5 minutes
Cost of fixing wrong problem: Spend 4 hours optimizing memory that's fine → no improvement to user experience
问题:Time Profiler显示JSON解析缓慢。Allocations显示内存使用正常。应该修复哪个?
答案:两个都是真实问题,优先级不同。
Time Profiler: JSONDecoder.decode() = 500ms
Allocations: Memory = 250MB(对于应用大小来说正常)
结果:应用运行缓慢,但内存正常
行动:优化JSON解码(而非内存)常见冲突:
| Time Profiler | Allocations | 行动 |
|---|---|---|
| 高CPU使用率 | 内存正常 | 优化计算(降低CPU消耗) |
| 低CPU使用率 | 内存持续增长 | 查找泄漏或减少对象创建 |
| 两者都高 | 两者都高 | 先分析对用户可见的问题 |
正确做法:
-
根据用户影响优先级处理
- 运行缓慢(UI卡顿)= 优先修复
- 内存问题(后台问题)= 次要修复
-
检查它们是否相关
- JSON解析是否泄漏内存?(否 → 独立问题)
- 内存增长是否会降低CPU性能?(可能 → 先修复内存)
-
按影响顺序修复
- JSON解析缓慢:影响每次数据加载
- 内存正常:无用户影响
- → 修复JSON解析
时间成本:5分钟(分析两个结果)= 5分钟
修复错误问题的成本:花费4小时优化正常的内存 → 用户体验没有任何改善
Scenario 3: "Profiling Under Deadline Pressure"
场景3:「截止日期前的性能分析」
The situation: Manager says "We ship in 2 hours. Is performance acceptable?"
Red flags you might think:
- "Profiling takes too long, let me just ask users"
- "I don't have time to profile properly, ship as-is"
- "One quick run will tell me if it's fine"
The reality: Profiling takes 15-20 minutes total. That's 1% of your remaining time.
What to do instead:
-
Profile the critical path (3 min)
- What users do most (load list, scroll, search)
- Not the entire app, just the slow part
-
Record one proper run (5 min)
- Cold cache first time
- Warm cache second time
- Use warm cache results
-
Interpret quickly (5 min)
- Time Profiler: Any >100ms on main thread? (If no, fine)
- Allocations: Any memory growing? (If no, fine)
-
Ship with confidence (2 min)
- If results are acceptable, ship
- If not, you have 90 minutes to fix or delay
Time cost: 15 min profiling + 5 min analysis = 20 minutes
Cost of not profiling: Ship with unknown performance → Users hit slowness → Bad reviews → Emergency hotfix 2 weeks later
Math: 20 minutes of profiling now << 2+ weeks of post-launch support
情况:经理说「我们2小时后发布。性能是否可以接受?」
你可能会想到的错误结论:
- 「性能分析太耗时,我问问用户吧」
- 「我没有时间正确分析,就这样发布吧」
- 「快速运行一次就能知道是否没问题」
实际情况:性能分析总共需要15-20分钟。这只占你剩余时间的1%。
正确做法:
-
分析关键路径(3分钟)
- 用户最常做的操作(加载列表、滚动、搜索)
- 不是整个应用,只是缓慢的部分
-
录制一次完整的运行(5分钟)
- 第一次是冷缓存
- 第二次是热缓存
- 使用热缓存的结果
-
快速解读(5分钟)
- Time Profiler:主线程是否有超过100ms的耗时?(如果没有,没问题)
- Allocations:内存是否持续增长?(如果没有,没问题)
-
自信地发布(2分钟)
- 如果结果可接受,发布
- 如果不可接受,你还有90分钟修复或延迟发布
时间成本:15分钟分析 + 5分钟解读 = 20分钟
不进行分析的成本:发布性能未知的应用 → 用户遇到运行缓慢问题 → 差评 → 2周后紧急修复
数学计算:现在花20分钟分析 << 发布后2周以上的支持工作
Quick Reference
快速参考
Common Operations
常见操作
swift
// Time Profiler: Launch Instruments
open -a Instruments
// Core Data: Enable SQL logging
// Edit Scheme → Run → Arguments Passed On Launch
-com.apple.CoreData.SQLDebug 1
// Allocations: Check persistent objects
Instruments → Allocations → Statistics → sort "Persistent"
// Memory warning: Simulate pressure
Xcode → Debug → Simulate Memory Warning
// Energy Impact: Profile battery drain
Instruments → Energy Impact template
// Network Link Conditioner: Simulate 3G
System Preferences → Network Link Conditioner → 3G profileswift
// Time Profiler: 启动Instruments
open -a Instruments
// Core Data: 启用SQL日志
// Edit Scheme → Run → Arguments Passed On Launch
-com.apple.CoreData.SQLDebug 1
// Allocations: 检查持久对象
Instruments → Allocations → Statistics → 按「Persistent」排序
// 内存警告:模拟压力
Xcode → Debug → Simulate Memory Warning
// Energy Impact: 分析电池消耗
Instruments → Energy Impact模板
// Network Link Conditioner: 模拟3G网络
System Preferences → Network Link Conditioner → 3G配置文件Decision Tree Summary
决策树摘要
Performance problem?
├─ App feels slow/laggy?
│ └─ → Time Profiler (measure CPU)
├─ Memory grows over time?
│ └─ → Allocations (find object growth)
├─ Data loading is slow?
│ └─ → Core Data instrument (if using Core Data)
│ └─ → Time Profiler (if computation slow)
└─ Battery drains fast?
└─ → Energy Impact (measure power)存在性能问题?
├─ 应用运行缓慢/卡顿?
│ └─ → Time Profiler(测量CPU)
├─ 内存持续增长?
│ └─ → Allocations(查找对象增长)
├─ 数据加载缓慢?
│ └─ → Core Data工具(如果使用了Core Data)
│ └─ → Time Profiler(如果是计算耗时)
└─ 电池消耗过快?
└─ → Energy Impact(测量功耗)Real-World Examples
真实案例
Example 1: Identifying N+1 Query Problem in Core Data
案例1:识别Core Data中的N+1查询问题
Scenario: Your app loads a list of albums with artist names. It's slow (5+ seconds for 100 albums). You suspect Core Data.
Setup: Enable SQL logging first
bash
undefined场景:你的应用加载包含艺术家名称的专辑列表,运行缓慢(100张专辑耗时5秒以上)。你怀疑是Core Data的问题。
设置:先启用SQL日志
bash
undefinedEdit Scheme → Run → Arguments Passed On Launch
Edit Scheme → Run → Arguments Passed On Launch
-com.apple.CoreData.SQLDebug 1
**What you see in console**:CoreData: sql: SELECT ... FROM albums WHERE ... (time: 0.050s)
CoreData: sql: SELECT ... FROM artists WHERE id = 1 (time: 0.003s)
CoreData: sql: SELECT ... FROM artists WHERE id = 2 (time: 0.003s)
... 98 more individual queries
Total: 0.050s + (100 × 0.003s) = 0.350s
**Diagnosis using the skill**:
- Fetching 100 albums, then individual query for each album's artist = **N+1 query problem** (Core Data Deep Dive, lines 302-325)
**Fix**:
```swift
// ❌ WRONG: Each album access triggers separate artist query
let request = Album.fetchRequest()
let albums = try context.fetch(request)
for album in albums {
print(album.artist.name) // Extra query for each
}
// ✅ RIGHT: Prefetch the relationship
let request = Album.fetchRequest()
request.returnsObjectsAsFaults = false
request.relationshipKeyPathsForPrefetching = ["artist"]
let albums = try context.fetch(request)
for album in albums {
print(album.artist.name) // Already loaded
}Result: 0.350s → 0.050s (7x faster)
-com.apple.CoreData.SQLDebug 1
**控制台显示**:CoreData: sql: SELECT ... FROM albums WHERE ... (time: 0.050s)
CoreData: sql: SELECT ... FROM artists WHERE id = 1 (time: 0.003s)
CoreData: sql: SELECT ... FROM artists WHERE id = 2 (time: 0.003s)
... 还有98次单独查询
总耗时:0.050s + (100 × 0.003s) = 0.350s
**使用本技能诊断**:
- 获取100张专辑,然后为每张专辑的艺术家执行单独查询 = **N+1查询问题**(Core Data深度解析,第302-325行)
**修复**:
```swift
// ❌ 错误:每张专辑的访问都会触发单独的艺术家查询
let request = Album.fetchRequest()
let albums = try context.fetch(request)
for album in albums {
print(album.artist.name) // 为每张专辑触发额外查询
}
// ✅ 正确:预加载关联关系
let request = Album.fetchRequest()
request.returnsObjectsAsFaults = false
request.relationshipKeyPathsForPrefetching = ["artist"]
let albums = try context.fetch(request)
for album in albums {
print(album.artist.name) // 已提前加载
}结果:0.350s → 0.050s(速度提升7倍)
Example 2: Finding Where UI Lag Really Comes From
案例2:找到UI卡顿的真正原因
Scenario: Your app UI stalls for 1-2 seconds when loading a view. Your co-lead says "Add background threading everywhere." You want to measure first.
Workflow using the skill (Time Profiler Deep Dive, lines 82-118):
- Open Instruments:
bash
open -a Instruments场景:你的应用加载视图时UI停滞1-2秒。你的主管说「所有操作都放到后台线程」。你想要先测量。
使用本技能的工作流(Time Profiler深度解析,第82-118行):
- 打开Instruments:
bash
open -a InstrumentsSelect "Time Profiler"
选择「Time Profiler」
2. **Record the stall**:App launches
Time Profiler records
View loads
Stall happens (observe the spike in Time Profiler)
Stop recording
3. **Examine results**:Call Stack shows:
viewDidLoad() – 1500ms
├─ loadJSON() – 1200ms (Self Time: 50ms)
│ └─ loadImages() – 1150ms (Self Time: 1150ms) ← HERE'S THE CULPRIT
├─ parseData() – 200ms
└─ layoutUI() – 100ms
4. **Apply the skill** (lines 173-175):loadJSON() has Self Time: 50ms, Total Time: 1200ms
→ loadJSON() isn't slow, something it CALLS is slow
→ loadImages() has Self Time: 1150ms
→ loadImages() is the actual bottleneck
5. **Fix the right thing**:
```swift
// ❌ WRONG: Thread everything
DispatchQueue.global().async { loadJSON() }
// ✅ RIGHT: Thread only the slow part
func loadJSON() {
let data = parseJSON() // 50ms, fine on main
// Move ONLY the slow part to background
DispatchQueue.global().async {
let images = loadImages() // 1150ms, now background
DispatchQueue.main.async {
updateUI(with: images)
}
}
}Result: 1500ms → 350ms (4x faster, main thread unblocked)
Why this matters: You fixed the ACTUAL bottleneck (1150ms), not guessing blindly about threading.
2. **录制停滞过程**:应用启动
Time Profiler开始录制
视图加载
发生停滞(观察Time Profiler中的尖峰)
停止录制
3. **检查结果**:调用栈显示:
viewDidLoad() – 1500ms
├─ loadJSON() – 1200ms(Self Time: 50ms)
│ └─ loadImages() – 1150ms(Self Time: 1150ms) ← 这是罪魁祸首
├─ parseData() – 200ms
└─ layoutUI() – 100ms
4. **应用本技能**(第173-175行):loadJSON()的Self Time: 50ms,Total Time: 1200ms
→ loadJSON()并不慢,是它调用的某个函数慢
→ loadImages()的Self Time: 1150ms
→ loadImages()是真正的瓶颈
5. **修复正确的问题**:
```swift
// ❌ 错误:所有操作都放到线程
DispatchQueue.global().async { loadJSON() }
// ✅ 正确:仅将缓慢的部分放到线程
func loadJSON() {
let data = parseJSON() // 50ms,在主线程执行没问题
// 仅将缓慢的部分移至后台
DispatchQueue.global().async {
let images = loadImages() // 1150ms,现在在后台执行
DispatchQueue.main.async {
updateUI(with: images)
}
}
}结果:1500ms → 350ms(速度提升4倍,主线程不再阻塞)
为什么这很重要:你修复了真正的瓶颈(1150ms),而非盲目猜测线程问题。
Example 3: Memory Growing vs Memory Leak
案例3:内存增长 vs 内存泄漏
Scenario: Allocations shows memory growing from 150MB to 600MB over 30 minutes of app use. Your manager says "Memory leak!" You need to know if it's real.
Workflow using the skill (Allocations Deep Dive, lines 199-277):
-
Launch Allocations in Instruments
-
Record normal app usage for 3 minutes:
User loads data → memory grows to 400MB
User navigates around → memory stays at 400MB
User goes to Settings → memory at 400MB
User comes back → memory at 400MB- Check Allocations Statistics:
Persistent Objects:
- UIImage: 1200 instances (300MB) ← Large count
- NSString: 5000 instances (4MB)
- CustomDataModel: 800 instances (15MB)- Ask the skill questions (lines 220-240):
- Are 1200 images legitimately loaded? (User loaded photo library with 1000 photos) → YES
- Does memory drop if you trigger memory warning? (Simulate with Xcode) → YES, drops to 180MB
- Is this caching working as designed? → YES
Diagnosis: NOT a leak. This is normal caching (lines 235-248)
Memory growing = apps using data users asked for
Memory dropping under pressure = cache working correctly
Memory staying high indefinitely = possible leak- Conclusion:
swift
// ✅ This is working correctly
let imageCache = NSCache<NSString, UIImage>()
// Holds up to 1200 images by design
// Clears when system memory pressure happens
// No leakResult: No action needed. The "leak" is actually the cache doing its job.
场景:Allocations显示30分钟的应用使用后,内存从150MB增长到600MB。你的经理说「内存泄漏!」你需要确认是否属实。
使用本技能的工作流(Allocations深度解析,第199-277行):
-
在Instruments中启动Allocations
-
录制3分钟的正常应用使用情况:
用户加载数据 → 内存增长到400MB
用户导航浏览 → 内存保持400MB
用户进入设置 → 内存保持400MB
用户返回 → 内存保持400MB- 检查Allocations统计信息:
持久对象:
- UIImage: 1200个实例(300MB)← 数量过多
- NSString: 5000个实例(4MB)
- CustomDataModel: 800个实例(15MB)- 根据本技能提问(第220-240行):
- 1200张图片是合理加载的吗?(用户加载了包含1000张照片的图库)→ 是
- 触发内存警告后内存是否下降?(使用Xcode模拟)→ 是,降至180MB
- 这是缓存的正常工作吗?→ 是
诊断:不是泄漏。这是正常的缓存(第235-248行)
内存增长 = 应用在使用用户请求的数据
内存压力下内存下降 = 缓存工作正常
内存无限期保持高位 = 可能存在泄漏- 结论:
swift
// ✅ 这是正常工作的
let imageCache = NSCache<NSString, UIImage>()
// 设计为最多存储1200张图片
// 系统内存压力时会清除缓存
// 没有泄漏结果:无需操作。所谓的「泄漏」实际上是缓存在正常工作。
Resources
资源
Docs: /library/archive/documentation/cocoa/conceptual/coredataperformance, /library/archive/technotes/tn2224
Skills: axiom-memory-debugging, axiom-swiftui-performance, axiom-swift-concurrency
Targets: iOS 14+, Swift 5.5+
Tools: Instruments, Core Data
History: See git log for changes
文档:/library/archive/documentation/cocoa/conceptual/coredataperformance, /library/archive/technotes/tn2224
技能:axiom-memory-debugging, axiom-swiftui-performance, axiom-swift-concurrency
目标平台: iOS 14+, Swift 5.5+
工具: Instruments, Core Data
历史记录: 查看git log了解变更