性能
泄漏先查谁把谁留住了
闭包、定时器、委托默认是强引用。退了页面内存不掉,就从引用关系往回看。
页面 pop 之后,Memory Graph 里这个控制器还在,就是被留下了。先找入边,不要先加 autoreleasepool。多数时候是闭包、Timer、通知、或者一个没断掉的委托。
闭包
逃逸闭包默认强引用 self。请求回调、动画完成、Combine 的 sink,都会把控制器留到回调结束。如果回调的生命周期比页面长,页面就泄漏。
final class EditorController: UIViewController {
private var saveTask: Task<Void, Never>?
func save() {
saveTask = Task { [weak self] in
guard let self else { return }
await self.store.save()
}
}
deinit {
saveTask?.cancel()
}
}
weak 解决的是环,不是所有捕获。如果闭包执行时页面必须还在,用 weak 后 guard 失败就返回,比强引用把页面钉死更合适。deinit 里取消任务,避免回调打到一半。
另外三条
- Timer 会强引用 target。用 block 版并在 deinit 里 invalidate,或者让定时器挂在一个单独的对象上。
- delegate 用 weak。双方互相 strong,图里就是一个环。
- NotificationCenter 用 block 观察时,要自己保存并移除 token。selector 版在 dealloc 时会摘掉,block 版不会。
确认方法:推入页面,退出,看这个类的实例数是否回到原来。增长一次就查一次,不要等内存警告再翻整张图。