Timer с target: self держит контроллер живым навсегда — исправьте
Этот контроллер запускает повторяющийся Timer, чтобы обновлять метку. Его deinit никогда не вызывается: после того как пользователь закрывает экран, контроллер навсегда остаётся в памяти.
Ограничения:
- Таймер должен продолжать срабатывать, пока экран виден.
- После pop контроллер должен освободиться (его
deinitдолжен выполниться).
final class ClockViewController: UIViewController {
var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(
timeInterval: 1, target: self,
selector: #selector(tick), userInfo: nil, repeats: true)
}
@objc func tick() { /* обновить метку */ }
}
Найдите и исправьте ошибку.
Запланированный Timer удерживает свой target, а run loop — таймер, поэтому target: self держит контроллер живым вечно. Исправьте вызовом invalidate() в viewDidDisappear или блочным API с [weak self].
- ✗Помечают свойство
Timerкакweak— это не разрывает удержание run loop - ✗Считают, что
Timerне удерживает свой target - ✗Ждут, что ARC или нехватка памяти всё равно освободят контроллер
- →Почему инвалидированный таймер наконец освобождает свой target?
- →Как блочный API
Timerпозволяет избежать сильногоtarget?
Цепочка удержаний такая: run loop -> Timer -> target (контроллер). Пометить свойство timer как weak бесполезно — удерживает не свойство, а run loop. Нужно либо инвалидировать таймер, когда экран уходит, либо перейти на блочный API, который не требует сильного target:
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
self?.tick()
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
timer?.invalidate() // run loop освобождает таймер, цепочка рвётся
}
invalidate() снимает таймер с run loop, тот освобождает его, и последняя сильная ссылка на контроллер исчезает — вызывается deinit.