Как обеспечить удержание дочерних объектов в памяти у родителя в Swift
Что проверяют
Понимаете ли вы ARC: кто владеет объектом, откуда берутся циклы сильных ссылок и какой тип ссылки ставить с каждой стороны. Следом почти всегда просят найти цикл в замыкании и объяснить разницу между weak и unowned.
Как отвечать
- родитель владеет детьми сильной ссылкой:
var children: [Child]илиlet child: Child. Пока жив родитель, счётчик ссылок ребёнка больше нуля, и ARC его не освободит. Массив тоже держит элементы сильно; - обратная ссылка слабая:
weak var parent: Parent?. Слабая ссылка не увеличивает счётчик и сама становитсяnil, когда объект освобождён, поэтому она всегда опциональная; unowned: когда ребёнок по смыслу не живёт дольше родителя, напримерunowned let owner: Parent. Опционал не нужен, но обращение после освобождения объекта аварийно завершит приложение;- замыкания: если ребёнок хранит замыкание, которое захватывает родителя, получается тот же цикл. Лечится списком захвата
[weak self]; - делегаты:
weak var delegate: SomeDelegate?, протокол для этого ограничивают классами черезAnyObject; - UIKit:
addSubviewиaddChild(_:)сами удерживают дочерние view и контроллеры, отдельный массив для них не нужен, аsuperviewиparentродителя не удерживают.
Утечки удобно искать через Memory Graph Debugger в Xcode и Instruments (Leaks), а в тестах через addTeardownBlock: проверяете, что слабая ссылка на объект после теста обнулилась.
Где спотыкаются
Делают обе ссылки сильными. Родитель и ребёнок держат друг друга, deinit не вызывается никогда, и память течёт без единого предупреждения компилятора.
Ставят weak на ссылку от родителя к ребёнку. Если больше никто ребёнка не держит, он освобождается сразу после присваивания, и свойство внезапно оказывается nil.
Считают unowned просто более удобным weak. Это обещание компилятору, что объект жив; нарушили его, получили краш, а не nil.