Task {} из viewDidLoad продолжает работать после закрытия экрана и удерживает self
Контроллер ниже запускает цикл опроса в viewDidLoad. После закрытия экрана цикл продолжает работать, и Instruments показывает, что контроллер не деаллоцируется.
Ограничение: сохраните поведение опроса; исправьте и время жизни, и утечку памяти.
final class FeedViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Task {
while true {
self.feed = try await self.api.poll()
try await Task.sleep(for: .seconds(5))
}
}
}
}
Найдите и исправьте обе ошибки.
Две ошибки. Неструктурированная Task {} не привязана к контроллеру — закрытие её не отменяет, и она работает дальше. И она сильно захватывает self, блокируя deinit. Фикс — сохраните задачу, cancel() при уходе, захват [weak self].
- ✗Думают, что неструктурированная
Task {}отменяется при deinit - ✗Считают, что
Task {}ожидают как структурированную дочернюю задачу - ✗Ждут, что
Task.detachedразом решит время жизни и захват
- →Почему сильный захват
selfдержит контроллер живым? - →Где в жизненном цикле UIKit нужно отменять хранимую задачу?
Задача не привязана к экрану, поэтому её нужно хранить и отменять самому; [weak self] разрывает сильную ссылку:
final class FeedViewController: UIViewController {
private var pollTask: Task<Void, Never>?
override func viewDidLoad() {
super.viewDidLoad()
pollTask = Task { [weak self] in
while let self, !Task.isCancelled {
self.feed = try? await self.api.poll()
try? await Task.sleep(for: .seconds(5))
}
}
}
deinit { pollTask?.cancel() }
}
Хранение задачи позволяет вызвать cancel() (неструктурированные задачи не отменяются за вас). [weak self] даёт контроллеру освободиться, и когда self становится nil, цикл выходит. Проверка Task.isCancelled делает кооперативную отмену реально останавливающей цикл, а Task.sleep бросает ошибку при отмене, поэтому ожидание сна разматывается сразу.