Может ли быть reference cycle с более чем двумя объектами в iOS
Что проверяют
Понимаете ли вы, что ARC считает только ссылки и циклы не ищет вообще, поэтому длина цикла роли не играет. Интервьюеру важно, узнаёте ли вы длинные циклы в реальном коде, где они не видны с первого взгляда. Следом спрашивают, где рвать такой цикл и как найти его в работающем приложении.
Как отвечать
Да, может. Объект освобождается, когда счётчик сильных ссылок падает до нуля. Если A держит B, B держит C, а C держит A, счётчики никогда не обнулятся, даже когда снаружи на эту тройку никто не ссылается. В отличие от сборщика мусора с обходом графа, ARC такую изолированную группу не заметит.
Покажите, где длинный цикл возникает на практике:
- Combine:
selfхранитSet<AnyCancellable>, подписка держит замыкание, замыкание захватываетself. Три звена, и ни одно не выглядит подозрительно; - координаторы: родитель держит массив дочерних, дочерний держит свой контроллер, контроллер держит координатор как сильного делегата;
- MVVM: контроллер держит view model, view model держит замыкание-колбэк, в котором сильно захвачен контроллер.
Чтобы разорвать цикл, достаточно одного слабого звена в любом месте кольца. Выбирать его стоит по владению: слабой делают обратную ссылку, то есть делегата, родителя, захват в замыкании через [weak self]. unowned подходит, только когда объект гарантированно живёт дольше ссылки, иначе обращение после освобождения уронит приложение.
Искать утечки удобно в Memory Graph Debugger в Xcode: он показывает живые объекты и связи между ними. Помогают и Instruments с инструментом Leaks, и простой print в deinit, который после закрытия экрана обязан сработать.
Где спотыкаются
Ищут только взаимную ссылку двух объектов и проверяют пары. Цикл из трёх звеньев при такой проверке проходит незамеченным.
Ставят weak на все ссылки подряд. Тогда объект без другого владельца освобождается сразу после создания, и вместо утечки получается пустой экран.
Считают [weak self] решением всех проблем: захват слабый, но внутри замыкания делают guard let self и запускают долгую операцию. Цикла нет, а экран живёт, пока она не закончится.