performance-optimization
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePerformance Optimization — Expert Decisions
性能优化——专业决策指南
Expert decision frameworks for performance choices. Claude knows lazy loading and async basics — this skill provides judgment calls for when to optimize and which tool to use.
性能优化决策的专业框架。Claude 了解懒加载和异步基础——本技能提供何时优化以及选择何种工具的判断依据。
Decision Trees
决策树
Should You Optimize?
是否应该进行优化?
When should you invest in optimization?
├─ User-facing latency issue (visible stutter/delay)
│ └─ YES — Profile and fix
│ Measure first, optimize second
│
├─ Premature concern ("this might be slow")
│ └─ NO — Wait for evidence
│ Write clean code, profile later
│
├─ Battery drain complaints
│ └─ YES — Use Energy Diagnostics
│ Focus on background work, location, network
│
├─ Memory warnings / crashes
│ └─ YES — Use Allocations + Leaks
│ Find retain cycles, unbounded caches
│
└─ App store reviews mention slowness
└─ YES — Profile real scenarios
User perception mattersThe trap: Optimizing based on assumptions. Always profile first. The bottleneck is rarely where you think.
何时应该投入优化工作?
├─ 用户可见的延迟问题(明显卡顿/延迟)
│ └─ 是 — 进行性能分析并修复
│ 先测量,后优化
│
├─ 过早担忧("这可能会变慢")
│ └─ 否 — 等待实际证据
│ 编写整洁代码,后续再进行性能分析
│
├─ 电池耗电投诉
│ └─ 是 — 使用 Energy Diagnostics
│ 重点关注后台任务、定位、网络
│
├─ 内存警告/崩溃
│ └─ 是 — 使用 Allocations + Leaks
│ 查找循环引用、无界缓存
│
└─ App Store 评论提及卡顿
└─ 是 — 针对真实场景进行性能分析
用户感知至关重要误区:基于假设进行优化。始终先做性能分析。性能瓶颈往往不在你预想的地方。
Profiling Tool Selection
性能分析工具选择
What are you measuring?
├─ Slow UI / frame drops
│ └─ Time Profiler + View Debugger
│ Find expensive work on main thread
│
├─ Memory growth / leaks
│ └─ Allocations + Leaks instruments
│ Track object lifetimes, find cycles
│
├─ Network performance
│ └─ Network instrument + Charles/Proxyman
│ Latency, payload size, request count
│
├─ Disk I/O issues
│ └─ File Activity instrument
│ Excessive reads/writes
│
├─ Battery drain
│ └─ Energy Log instrument
│ CPU wake, location, networking
│
└─ GPU / rendering
└─ Core Animation instrument
Offscreen rendering, overdraw你需要测量什么?
├─ 界面卡顿/掉帧
│ └─ Time Profiler + View Debugger
│ 查找主线程上的高负载任务
│
├─ 内存增长/泄漏
│ └─ Allocations + Leaks 工具
│ 追踪对象生命周期,查找循环引用
│
├─ 网络性能
│ └─ Network 工具 + Charles/Proxyman
│ 延迟、 payload 大小、请求数量
│
├─ 磁盘 I/O 问题
│ └─ File Activity 工具
│ 过度读写操作
│
├─ 电池耗电
│ └─ Energy Log 工具
│ CPU 唤醒、定位、网络活动
│
└─ GPU/渲染问题
└─ Core Animation 工具
离屏渲染、过度绘制SwiftUI View Update Strategy
SwiftUI 视图更新策略
View is re-rendering too often?
├─ Caused by parent state changes
│ └─ Extract to separate view
│ Child doesn't depend on changing state
│
├─ Complex computed body
│ └─ Cache expensive computations
│ Use ViewModel or memoization
│
├─ List items all updating
│ └─ Check view identity
│ Use stable IDs, not indices
│
├─ Observable causing cascading updates
│ └─ Split into multiple @Published
│ Or use computed properties
│
└─ Animation causing constant redraws
└─ Use drawingGroup() or limit scope
Rasterize stable content视图过于频繁重渲染?
├─ 由父视图状态变更导致
│ └─ 提取为独立视图
│ 子视图不依赖变更的状态
│
├─ 复杂的计算式 body
│ └─ 缓存昂贵的计算结果
│ 使用 ViewModel 或记忆化技术
│
├─ 列表所有项都在更新
│ └─ 检查视图标识
│ 使用稳定 ID,而非索引
│
├─ Observable 对象导致级联更新
│ └─ 拆分为多个 @Published 属性
│ 或使用计算属性
│
└─ 动画导致持续重绘
└─ 使用 drawingGroup() 或限制作用域
栅格化稳定内容Memory Management Decision
内存管理决策
How to fix memory issues?
├─ Steady growth during use
│ └─ Check caches and collections
│ Add eviction, use NSCache
│
├─ Growth tied to navigation
│ └─ Check retain cycles
│ weak self in closures, delegates
│
├─ Large spikes on specific screens
│ └─ Downsample images
│ Load at display size, not full resolution
│
├─ Memory not released after screen dismissal
│ └─ Debug object lifecycle
│ deinit not called = retain cycle
│
└─ Background memory pressure
└─ Respond to didReceiveMemoryWarning
Clear caches, release non-essential data如何修复内存问题?
├─ 使用过程中内存持续增长
│ └─ 检查缓存和集合
│ 添加淘汰机制,使用 NSCache
│
├─ 内存增长与导航相关
│ └─ 检查循环引用
│ 闭包中使用 weak self,代理使用弱引用
│
├─ 特定界面出现内存大幅飙升
│ └─ 图片降采样
│ 按显示尺寸加载,而非全分辨率
│
├─ 界面关闭后内存未释放
│ └─ 调试对象生命周期
│ deinit 未调用 = 存在循环引用
│
└─ 后台内存压力
└─ 响应 didReceiveMemoryWarning
清理缓存,释放非必要数据NEVER Do
切勿执行的操作
View Identity
视图标识
NEVER use indices as identifiers:
swift
// ❌ Identity changes when array mutates
List(items.indices, id: \.self) { index in
ItemRow(item: items[index])
}
// Insert at index 0 → all views recreated!
// ✅ Use stable identifiers
List(items) { item in
ItemRow(item: item)
.id(item.id) // Stable across mutations
}NEVER compute expensive values in body:
swift
// ❌ Called on every render
var body: some View {
let sortedItems = items.sorted { $0.date > $1.date } // O(n log n) per render!
let filtered = sortedItems.filter { $0.isActive }
List(filtered) { item in
ItemRow(item: item)
}
}
// ✅ Compute in ViewModel or use computed property
@MainActor
class ViewModel: ObservableObject {
@Published var items: [Item] = []
var displayItems: [Item] {
items.filter(\.isActive).sorted { $0.date > $1.date }
}
}切勿使用索引作为标识符:
swift
// ❌ 数组变更时标识会改变
List(items.indices, id: \.self) { index in
ItemRow(item: items[index])
}
// 在索引0处插入元素 → 所有视图都会被重新创建!
// ✅ 使用稳定的标识符
List(items) { item in
ItemRow(item: item)
.id(item.id) // 数组变更时保持稳定
}切勿在 body 中计算昂贵的值:
swift
// ❌ 每次渲染都会调用
var body: some View {
let sortedItems = items.sorted { $0.date > $1.date } // 每次渲染都是 O(n log n) 复杂度!
let filtered = sortedItems.filter { $0.isActive }
List(filtered) { item in
ItemRow(item: item)
}
}
// ✅ 在 ViewModel 中计算或使用计算属性
@MainActor
class ViewModel: ObservableObject {
@Published var items: [Item] = []
var displayItems: [Item] {
items.filter(\.isActive).sorted { $0.date > $1.date }
}
}State Management
状态管理
NEVER use @StateObject for passed objects:
swift
// ❌ Creates new instance on every parent update
struct ChildView: View {
@StateObject var viewModel: ChildViewModel // Wrong!
var body: some View { ... }
}
// ✅ Use @ObservedObject for passed objects
struct ChildView: View {
@ObservedObject var viewModel: ChildViewModel // Parent owns it
var body: some View { ... }
}NEVER make everything @Published:
swift
// ❌ Every property change triggers view updates
class ViewModel: ObservableObject {
@Published var items: [Item] = []
@Published var internalCache: [String: Data] = [:] // UI doesn't need this!
@Published var isProcessing = false // Maybe internal only
}
// ✅ Only publish what UI observes
class ViewModel: ObservableObject {
@Published var items: [Item] = []
@Published var isLoading = false
private var internalCache: [String: Data] = [:] // Not @Published
private var isProcessing = false // Private state
}切勿为传入的对象使用 @StateObject:
swift
// ❌ 父视图更新时会创建新实例
struct ChildView: View {
@StateObject var viewModel: ChildViewModel // 错误用法!
var body: some View { ... }
}
// ✅ 为传入的对象使用 @ObservedObject
struct ChildView: View {
@ObservedObject var viewModel: ChildViewModel // 由父视图持有
var body: some View { ... }
}切勿将所有属性都标记为 @Published:
swift
// ❌ 任何属性变更都会触发视图更新
class ViewModel: ObservableObject {
@Published var items: [Item] = []
@Published var internalCache: [String: Data] = [:] // UI 不需要关注这个!
@Published var isProcessing = false // 可能仅内部使用
}
// ✅ 仅发布 UI 需要观察的属性
class ViewModel: ObservableObject {
@Published var items: [Item] = []
@Published var isLoading = false
private var internalCache: [String: Data] = [:] // 不标记为 @Published
private var isProcessing = false // 私有状态
}Memory Leaks
内存泄漏
NEVER capture self strongly in escaping closures:
swift
// ❌ Retain cycle — never deallocates
class ViewModel {
var timer: Timer?
func start() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
self.tick() // Strong capture!
}
}
}
// ✅ Weak capture + invalidation
class ViewModel {
var timer: Timer?
func start() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.tick()
}
}
deinit {
timer?.invalidate()
}
}NEVER forget to remove observers:
swift
// ❌ Leaks observer and potentially self
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(handleNotification),
name: .userLoggedIn,
object: nil
)
// Never removed!
}
}
// ✅ Remove in deinit or use modern API
class ViewController: UIViewController {
private var observer: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
observer = NotificationCenter.default.addObserver(
forName: .userLoggedIn,
object: nil,
queue: .main
) { [weak self] _ in
self?.handleNotification()
}
}
deinit {
if let observer { NotificationCenter.default.removeObserver(observer) }
}
}切勿在逃逸闭包中强引用 self:
swift
// ❌ 循环引用 — 对象永远无法释放
class ViewModel {
var timer: Timer?
func start() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
self.tick() // 强引用!
}
}
}
// ✅ 弱引用 + 失效处理
class ViewModel {
var timer: Timer?
func start() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.tick()
}
}
deinit {
timer?.invalidate()
}
}切勿忘记移除观察者:
swift
// ❌ 泄漏观察者及可能的 self
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(handleNotification),
name: .userLoggedIn,
object: nil
)
// 从未移除!
}
}
// ✅ 在 deinit 中移除或使用现代 API
class ViewController: UIViewController {
private var observer: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
observer = NotificationCenter.default.addObserver(
forName: .userLoggedIn,
object: nil,
queue: .main
) { [weak self] _ in
self?.handleNotification()
}
}
deinit {
if let observer { NotificationCenter.default.removeObserver(observer) }
}
}Image Loading
图片加载
NEVER load full resolution for thumbnails:
swift
// ❌ 4000×3000 image for 80×80 thumbnail
let image = UIImage(contentsOfFile: path) // Full resolution in memory!
imageView.image = image
// ✅ Downsample to display size
func downsampledImage(at url: URL, to size: CGSize) -> UIImage? {
let options: [CFString: Any] = [
kCGImageSourceShouldCache: false,
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height) * UIScreen.main.scale
]
guard let source = CGImageSourceCreateWithURL(url as CFURL, nil),
let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary) else {
return nil
}
return UIImage(cgImage: cgImage)
}NEVER cache images without limits:
swift
// ❌ Unbounded memory growth
class ImageLoader {
private var cache: [URL: UIImage] = [:] // Grows forever!
func image(for url: URL) -> UIImage? {
if let cached = cache[url] { return cached }
let image = loadImage(url)
cache[url] = image // Never evicted
return image
}
}
// ✅ Use NSCache with limits
class ImageLoader {
private let cache = NSCache<NSURL, UIImage>()
init() {
cache.countLimit = 100
cache.totalCostLimit = 50 * 1024 * 1024 // 50 MB
}
func image(for url: URL) -> UIImage? {
if let cached = cache.object(forKey: url as NSURL) { return cached }
guard let image = loadImage(url) else { return nil }
cache.setObject(image, forKey: url as NSURL, cost: image.jpegData(compressionQuality: 1)?.count ?? 0)
return image
}
}切勿为缩略图加载全分辨率图片:
swift
// ❌ 为 80×80 缩略图加载 4000×3000 图片
let image = UIImage(contentsOfFile: path) // 全分辨率图片加载到内存中!
imageView.image = image
// ✅ 降采样至显示尺寸
func downsampledImage(at url: URL, to size: CGSize) -> UIImage? {
let options: [CFString: Any] = [
kCGImageSourceShouldCache: false,
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height) * UIScreen.main.scale
]
guard let source = CGImageSourceCreateWithURL(url as CFURL, nil),
let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary) else {
return nil
}
return UIImage(cgImage: cgImage)
}切勿无限制缓存图片:
swift
// ❌ 内存无限制增长
class ImageLoader {
private var cache: [URL: UIImage] = [:] // 持续增长!
func image(for url: URL) -> UIImage? {
if let cached = cache[url] { return cached }
let image = loadImage(url)
cache[url] = image // 从未淘汰
return image
}
}
// ✅ 使用带限制的 NSCache
class ImageLoader {
private let cache = NSCache<NSURL, UIImage>()
init() {
cache.countLimit = 100
cache.totalCostLimit = 50 * 1024 * 1024 // 50 MB
}
func image(for url: URL) -> UIImage? {
if let cached = cache.object(forKey: url as NSURL) { return cached }
guard let image = loadImage(url) else { return nil }
cache.setObject(image, forKey: url as NSURL, cost: image.jpegData(compressionQuality: 1)?.count ?? 0)
return image
}
}Heavy Operations
重负载操作
NEVER do heavy work on main thread:
swift
// ❌ UI frozen during processing
func loadData() {
let data = try! Data(contentsOf: largeFileURL) // Blocks main thread!
let parsed = parseData(data) // Still blocking!
self.items = parsed
}
// ✅ Use background thread, update on main
func loadData() async {
let items = await Task.detached(priority: .userInitiated) {
let data = try! Data(contentsOf: largeFileURL)
return parseData(data)
}.value
await MainActor.run {
self.items = items
}
}切勿在主线程执行重负载工作:
swift
// ❌ 处理过程中 UI 冻结
func loadData() {
let data = try! Data(contentsOf: largeFileURL) // 阻塞主线程!
let parsed = parseData(data) // 仍在阻塞!
self.items = parsed
}
// ✅ 使用后台线程,主线程更新 UI
func loadData() async {
let items = await Task.detached(priority: .userInitiated) {
let data = try! Data(contentsOf: largeFileURL)
return parseData(data)
}.value
await MainActor.run {
self.items = items
}
}Essential Patterns
核心模式
Efficient List View
高效列表视图
swift
struct EfficientListView: View {
let items: [Item]
var body: some View {
ScrollView {
LazyVStack(spacing: 12) { // Lazy = on-demand creation
ForEach(items) { item in
ItemRow(item: item)
.id(item.id) // Stable identity
}
}
}
}
}
// Equatable row prevents unnecessary updates
struct ItemRow: View, Equatable {
let item: Item
var body: some View {
HStack {
AsyncImage(url: item.imageURL) { image in
image.resizable().aspectRatio(contentMode: .fill)
} placeholder: {
Color.gray.opacity(0.3)
}
.frame(width: 60, height: 60)
.clipShape(RoundedRectangle(cornerRadius: 8))
VStack(alignment: .leading) {
Text(item.title).font(.headline)
Text(item.subtitle).font(.caption).foregroundColor(.secondary)
}
}
}
static func == (lhs: ItemRow, rhs: ItemRow) -> Bool {
lhs.item.id == rhs.item.id &&
lhs.item.title == rhs.item.title &&
lhs.item.subtitle == rhs.item.subtitle
}
}swift
struct EfficientListView: View {
let items: [Item]
var body: some View {
ScrollView {
LazyVStack(spacing: 12) { // Lazy = 按需创建视图
ForEach(items) { item in
ItemRow(item: item)
.id(item.id) // 稳定的标识
}
}
}
}
}
// 实现 Equatable 协议避免不必要的更新
struct ItemRow: View, Equatable {
let item: Item
var body: some View {
HStack {
AsyncImage(url: item.imageURL) { image in
image.resizable().aspectRatio(contentMode: .fill)
} placeholder: {
Color.gray.opacity(0.3)
}
.frame(width: 60, height: 60)
.clipShape(RoundedRectangle(cornerRadius: 8))
VStack(alignment: .leading) {
Text(item.title).font(.headline)
Text(item.subtitle).font(.caption).foregroundColor(.secondary)
}
}
}
static func == (lhs: ItemRow, rhs: ItemRow) -> Bool {
lhs.item.id == rhs.item.id &&
lhs.item.title == rhs.item.title &&
lhs.item.subtitle == rhs.item.subtitle
}
}Memory-Safe ViewModel
内存安全的 ViewModel
swift
@MainActor
final class ViewModel: ObservableObject {
@Published private(set) var items: [Item] = []
@Published private(set) var isLoading = false
private var cancellables = Set<AnyCancellable>()
private var loadTask: Task<Void, Never>?
func load() {
loadTask?.cancel() // Cancel previous
loadTask = Task {
guard !Task.isCancelled else { return }
isLoading = true
defer { isLoading = false }
do {
let items = try await API.fetchItems()
guard !Task.isCancelled else { return }
self.items = items
} catch {
// Handle error
}
}
}
deinit {
loadTask?.cancel()
cancellables.removeAll()
}
}swift
@MainActor
final class ViewModel: ObservableObject {
@Published private(set) var items: [Item] = []
@Published private(set) var isLoading = false
private var cancellables = Set<AnyCancellable>()
private var loadTask: Task<Void, Never>?
func load() {
loadTask?.cancel() // 取消之前的任务
loadTask = Task {
guard !Task.isCancelled else { return }
isLoading = true
defer { isLoading = false }
do {
let items = try await API.fetchItems()
guard !Task.isCancelled else { return }
self.items = items
} catch {
// 处理错误
}
}
}
deinit {
loadTask?.cancel()
cancellables.removeAll()
}
}Debounced Search
防抖搜索
swift
@MainActor
final class SearchViewModel: ObservableObject {
@Published var searchText = ""
@Published private(set) var results: [Item] = []
private var searchTask: Task<Void, Never>?
init() {
// Debounce search
$searchText
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
.removeDuplicates()
.sink { [weak self] text in
self?.performSearch(text)
}
.store(in: &cancellables)
}
private func performSearch(_ query: String) {
searchTask?.cancel()
guard !query.isEmpty else {
results = []
return
}
searchTask = Task {
do {
let results = try await API.search(query: query)
guard !Task.isCancelled else { return }
self.results = results
} catch {
// Handle error
}
}
}
}swift
@MainActor
final class SearchViewModel: ObservableObject {
@Published var searchText = ""
@Published private(set) var results: [Item] = []
private var searchTask: Task<Void, Never>?
init() {
// 防抖搜索
$searchText
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
.removeDuplicates()
.sink { [weak self] text in
self?.performSearch(text)
}
.store(in: &cancellables)
}
private func performSearch(_ query: String) {
searchTask?.cancel()
guard !query.isEmpty else {
results = []
return
}
searchTask = Task {
do {
let results = try await API.search(query: query)
guard !Task.isCancelled else { return }
self.results = results
} catch {
// 处理错误
}
}
}
}Quick Reference
快速参考
Instruments Selection
Instruments 工具选择
| Issue | Instrument | What to Look For |
|---|---|---|
| Slow UI | Time Profiler | Heavy main thread work |
| Memory leak | Leaks | Leaked objects |
| Memory growth | Allocations | Growing categories |
| Battery | Energy Log | Wake frequency |
| Network | Network | Request count, size |
| Disk | File Activity | Excessive I/O |
| GPU | Core Animation | Offscreen renders |
| 问题 | 工具 | 检查要点 |
|---|---|---|
| 界面卡顿 | Time Profiler | 主线程高负载任务 |
| 内存泄漏 | Leaks | 泄漏的对象 |
| 内存增长 | Allocations | 持续增长的对象类别 |
| 电池耗电 | Energy Log | 唤醒频率 |
| 网络问题 | Network | 请求数量、大小 |
| 磁盘问题 | File Activity | 过度 I/O 操作 |
| GPU 问题 | Core Animation | 离屏渲染 |
SwiftUI Performance Checklist
SwiftUI 性能检查清单
| Issue | Solution |
|---|---|
| Slow list scrolling | Use LazyVStack/LazyVGrid |
| All items re-render | Stable IDs, Equatable rows |
| Heavy body computation | Move to ViewModel |
| Cascading @Published updates | Split or use computed |
| Animation jank | Use drawingGroup() |
| 问题 | 解决方案 |
|---|---|
| 列表滚动卡顿 | 使用 LazyVStack/LazyVGrid |
| 所有列表项重渲染 | 使用稳定 ID、实现 Equatable 的行视图 |
| body 计算成本高 | 迁移至 ViewModel |
| @Published 级联更新 | 拆分属性或使用计算属性 |
| 动画导致的卡顿 | 使用 drawingGroup() |
Memory Management
内存管理
| Pattern | Prevent Issue |
|---|---|
| [weak self] in closures | Retain cycles |
| Timer.invalidate() in deinit | Timer leaks |
| Remove observers in deinit | Observer leaks |
| NSCache with limits | Unbounded cache growth |
| Image downsampling | Memory spikes |
| 模式 | 预防的问题 |
|---|---|
| 闭包中使用 [weak self] | 循环引用 |
| deinit 中调用 Timer.invalidate() | Timer 泄漏 |
| deinit 中移除观察者 | 观察者泄漏 |
| 带限制的 NSCache | 无界缓存增长 |
| 图片降采样 | 内存飙升 |
os_signpost for Custom Profiling
自定义性能分析的 os_signpost
swift
import os.signpost
let log = OSLog(subsystem: "com.app", category: .pointsOfInterest)
os_signpost(.begin, log: log, name: "DataProcessing")
// Expensive work
os_signpost(.end, log: log, name: "DataProcessing")swift
import os.signpost
let log = OSLog(subsystem: "com.app", category: .pointsOfInterest)
os_signpost(.begin, log: log, name: "DataProcessing")
// 耗时操作
os_signpost(.end, log: log, name: "DataProcessing")Red Flags
危险信号
| Smell | Problem | Fix |
|---|---|---|
| Indices as List IDs | Views recreated on mutation | Use stable identifiers |
| Expensive body computation | Runs every render | Move to ViewModel |
| @StateObject for passed object | Creates new instance | Use @ObservedObject |
| Strong self in Timer/closure | Retain cycle | Use [weak self] |
| Full-res images for thumbnails | Memory explosion | Downsample to display size |
| Unbounded dictionary cache | Memory growth | Use NSCache with limits |
| Heavy work without Task.detached | Blocks main thread | Use background priority |
| 代码异味 | 问题 | 修复方案 |
|---|---|---|
| 使用索引作为 List ID | 数组变更时视图被重新创建 | 使用稳定标识符 |
| body 中执行昂贵计算 | 每次渲染都执行 | 迁移至 ViewModel |
| 为传入对象使用 @StateObject | 创建新实例 | 使用 @ObservedObject |
| Timer/闭包中强引用 self | 循环引用 | 使用 [weak self] |
| 为缩略图加载全分辨率图片 | 内存暴涨 | 降采样至显示尺寸 |
| 使用无界字典缓存 | 内存持续增长 | 使用带限制的 NSCache |
| 重负载操作未使用 Task.detached | 阻塞主线程 | 使用后台优先级任务 |