performance-optimization

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Performance 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 matters
The 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 工具选择

IssueInstrumentWhat to Look For
Slow UITime ProfilerHeavy main thread work
Memory leakLeaksLeaked objects
Memory growthAllocationsGrowing categories
BatteryEnergy LogWake frequency
NetworkNetworkRequest count, size
DiskFile ActivityExcessive I/O
GPUCore AnimationOffscreen renders
问题工具检查要点
界面卡顿Time Profiler主线程高负载任务
内存泄漏Leaks泄漏的对象
内存增长Allocations持续增长的对象类别
电池耗电Energy Log唤醒频率
网络问题Network请求数量、大小
磁盘问题File Activity过度 I/O 操作
GPU 问题Core Animation离屏渲染

SwiftUI Performance Checklist

SwiftUI 性能检查清单

IssueSolution
Slow list scrollingUse LazyVStack/LazyVGrid
All items re-renderStable IDs, Equatable rows
Heavy body computationMove to ViewModel
Cascading @Published updatesSplit or use computed
Animation jankUse drawingGroup()
问题解决方案
列表滚动卡顿使用 LazyVStack/LazyVGrid
所有列表项重渲染使用稳定 ID、实现 Equatable 的行视图
body 计算成本高迁移至 ViewModel
@Published 级联更新拆分属性或使用计算属性
动画导致的卡顿使用 drawingGroup()

Memory Management

内存管理

PatternPrevent Issue
[weak self] in closuresRetain cycles
Timer.invalidate() in deinitTimer leaks
Remove observers in deinitObserver leaks
NSCache with limitsUnbounded cache growth
Image downsamplingMemory 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

危险信号

SmellProblemFix
Indices as List IDsViews recreated on mutationUse stable identifiers
Expensive body computationRuns every renderMove to ViewModel
@StateObject for passed objectCreates new instanceUse @ObservedObject
Strong self in Timer/closureRetain cycleUse [weak self]
Full-res images for thumbnailsMemory explosionDownsample to display size
Unbounded dictionary cacheMemory growthUse NSCache with limits
Heavy work without Task.detachedBlocks main threadUse background priority
代码异味问题修复方案
使用索引作为 List ID数组变更时视图被重新创建使用稳定标识符
body 中执行昂贵计算每次渲染都执行迁移至 ViewModel
为传入对象使用 @StateObject创建新实例使用 @ObservedObject
Timer/闭包中强引用 self循环引用使用 [weak self]
为缩略图加载全分辨率图片内存暴涨降采样至显示尺寸
使用无界字典缓存内存持续增长使用带限制的 NSCache
重负载操作未使用 Task.detached阻塞主线程使用后台优先级任务