Каким образом ARC определяет момент освобождения экземпляра класса, если ссылки на него исчезают в разном порядке?
ARC освобождает экземпляр класса, когда количество его сильных ссылок становится равным нулю. Порядок уничтожения самих ссылок не важен: важен их текущий суммарный счётчик. Если между объектами образовался сильный цикл ссылок, счётчик не обнулится автоматически, поэтому экземпляры не будут освобождены.
Управление временем жизни объектов решает проблему безопасного освобождения ссылочных экземпляров без ручных операций retain и release. В Swift эту работу выполняет Automatic Reference Counting: компилятор добавляет необходимые операции управления сильными ссылками, сохраняя предсказуемый момент уничтожения объектов.
В отличие от сборщика мусора, ARC не выполняет периодический глобальный поиск недостижимых объектов. Поэтому циклические зависимости нужно разрывать в модели данных явно.
Один экземпляр класса может быть доступен через несколько сильных ссылок: локальные переменные, свойства и элементы коллекций. Удаление одной ссылки не означает немедленное уничтожение объекта, если другие сильные ссылки всё ещё существуют.
Неверная оценка времени жизни приводит к двум типичным ошибкам: попытке обратиться к объекту после ожидаемого освобождения или утечке памяти из-за сильного цикла. Особенно часто цикл возникает, когда объект хранит замыкание, а замыкание сильно захватывает этот же объект.
Каждая сильная ссылка увеличивает число владельцев экземпляра, а исчезновение сильной ссылки уменьшает его. Когда последний сильный владелец исчезает, Swift освобождает экземпляр и вызывает его deinit, если он объявлен.
Слабая ссылка, объявленная как weak, не увеличивает счётчик и автоматически становится nil после освобождения объекта. Она всегда должна быть необязательной. Ссылка unowned также не удерживает объект, но не обнуляется; обращение к ней после уничтожения объекта приводит к ошибке выполнения.
В примере замыкание хранится в Owner, но захватывает self слабо. Поэтому после присваивания nil внешней ссылке сильных владельцев не остаётся, и экземпляр освобождается. При сильном захвате self возник бы цикл: Owner удерживает замыкание, а замыкание удерживает Owner.
Weak подходит, когда объект может исчезнуть раньше потребителя, например для делегатов и обработчиков событий. Unowned уместен только при доказуемо более долгом времени жизни объекта-владельца; его преимущество — отсутствие optional-проверки, но цена ошибки выше.
Экран приложения создаёт объект модели и передаёт ему замыкание обновления интерфейса. После закрытия экрана память не освобождается, потому что модель хранит замыкание, а замыкание сильно захватывает контроллер; контроллер, в свою очередь, удерживает модель.
Первый вариант — сделать все связи слабыми. Это снижает риск цикла, но может привести к тому, что нужный объект исчезнет до выполнения события. Второй вариант — использовать unowned; он сохраняет удобный невoptional-доступ, но опасен при ошибке в жизненном цикле.
Выбранное решение — захватывать контроллер через weak, если обработка события допустима только при его существовании. После закрытия экрана контроллер освобождается, замыкание перестаёт выполнять работу, а утечка устраняется без риска обращения к уничтоженному объекту.
Увеличивается ли счётчик ссылок при передаче экземпляра класса в функцию?
Да, передача сильной ссылки обычно создаёт ещё одного сильного владельца на время действия соответствующего параметра. При выходе из функции этот владелец исчезает. Сам экземпляр при этом не копируется: копируется ссылка на него.
Гарантирует ли ARC немедленный вызов deinit сразу после присваивания последней переменной nil?
Для обычных сильных ссылок уничтожение происходит детерминированно после исчезновения последнего сильного владения, но не следует делать выводы о точном порядке освобождения сложных временных значений, оптимизированных компилятором. Код не должен зависеть от побочных эффектов порядка уничтожения независимых объектов.
Почему слабая ссылка не всегда является правильным способом разорвать цикл?
weak разрывает владение, но объект может исчезнуть до выполнения замыкания или операции. Если зависимость по смыслу обязательна и гарантированно соблюдает порядок жизни, unowned точнее выражает контракт; если такой гарантии нет, нужно использовать weak и корректно обработать nil.