Найдите утечку за ростом памяти при каждом push и pop экрана
Каждый раз, когда пользователь открывает DetailViewController и закрывает его, память приложения растёт и не возвращается назад. Вы снимаете Memory Graph Debugger после трёх закрытий экрана и видите три живых экземпляра:
DetailViewController 0x600001a (3 living instances)
|- strong --> closure (onData) 0x600002b
| |- strong --> DetailViewController 0x600001a
|- strong --> UITableView 0x600003c
Root set: (nothing holds DetailViewController)
Объясните, почему экземпляры остаются живыми, и дайте однострочное исправление.
Граф показывает, что закрытый контроллер всё ещё жив: сильные стрелки к замыканию и обратно образуют цикл, поэтому он не освобождается. Захватите [weak self], и контроллер освободится при pop, прекращая рост.
- ✗Винят кэши в ровном росте вместо проверки графа удержаний
- ✗Ждут, что
viewDidDisappearили ручное обнуление освободит зациклённый контроллер - ✗Помечают
unownedвсе рёбра вместо разрыва одного черезweak
- →Как отличить сильную стрелку от
weakв графе? - →Почему утёкший контроллер часто тянет за собой всю иерархию view?
Стрелки в графе — сильные, и они образуют петлю: контроллер сильно держит замыкание onData, а замыкание захватило self сильно и держит контроллер обратно. Ни один счётчик не доходит до нуля, поэтому при каждом pop остаётся ещё один живой экземпляр. Разрываем цикл захватом [weak self]:
final class DetailViewController: UIViewController {
var onData: ((Data) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
onData = { [weak self] data in // было: сильный захват self
self?.apply(data)
}
}
func apply(_ data: Data) { /* ... */ }
}
Теперь замыкание не удерживает контроллер, счётчик доходит до нуля при pop, вызывается deinit, и рост памяти прекращается.