Программирование SwiftARC и памятьСтарший разработчик iOS на Swift

Представьте объект, на который слабо ссылаются тысячи владельцев. Как ARC обнулит эти ссылки при уничтожени...

Представьте объект, на который слабо ссылаются тысячи владельцев. Как ARC обнулит эти ссылки при уничтожении объекта без полного перебора памяти?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

ARC использует специальное внутреннее учётное хранилище для weak-ссылок объекта, поэтому при его уничтожении runtime обращается к зарегистрированным слабым ссылкам и обнуляет их. Полный перебор всей памяти процесса не требуется.

Исторический контекст

ARC появился как автоматизация ручного управления подсчёком ссылок, чтобы разработчик не расставлял операции удержания и освобождения объекта вручную. При этом слабые ссылки потребовали отдельного механизма: они не должны увеличивать счётчик сильных ссылок, но должны становиться безопасными после уничтожения объекта.

Постановка проблемы

Слабая ссылка не владеет объектом. Если объект уничтожен, сохранённый в ней адрес становится недействительным; простое оставление этого адреса привело бы к обращению к освобождённой памяти.

Наивный перебор всех объектов и всех ссылок процесса был бы слишком дорогим. Кроме того, runtime должен корректно обрабатывать создание, копирование, чтение и удаление слабых ссылок во время многопоточного выполнения.

Подробное решение

При создании weak-ссылки runtime регистрирует её в служебной структуре, связанной с объектом. В реализациях Swift это обычно описывают как таблицу слабых ссылок или side table, но точная внутренняя организация является деталью реализации и не гарантируется языком.

Когда последний владелец освобождает объект и объект действительно переходит к уничтожению, runtime находит зарегистрированные weak-ссылки, делает их nil, а затем завершает освобождение объекта. Поэтому чтение weak-ссылки после уничтожения возвращает nil, а не прежний адрес.

Служебный учёт имеет цену: weak обычно сложнее и дороже сильной ссылки по памяти и времени. Это одна из причин, по которой weak применяют для не владеющих связей, а не используют без необходимости.

Важно отличать это от unowned. Невладеющая ссылка не регистрируется как обнуляемая weak-ссылка: после уничтожения объекта она не превращается в nil, поэтому обращение к ней может завершиться ошибкой выполнения.

Ситуация из практики

В приложении экран создаёт объект модели, а десятки временных наблюдателей хранят weak-ссылки на эту модель. После закрытия экрана сильные ссылки исчезают, и модель уничтожается.

Вариант с сильными ссылками прост, но создаёт риск, что наблюдатели будут удерживать модель дольше необходимого или образуют цикл. Вариант с unowned дешевле, однако требует строгой гарантии, что каждый наблюдатель исчезнет раньше модели; ошибка даст обращение к уничтоженному объекту.

Выбор weak оправдан, если наблюдатели могут пережить модель. Runtime обнулит все зарегистрированные ссылки, после чего они безопасно перестанут выполнять действия. Результат — отсутствие retain cycle и отсутствие обращения к освобождённой памяти, ценой дополнительного служебного учёта.

Что кандидаты часто упускают

  1. Обнуляет ли ARC weak-ссылки немедленно при достижении нулевого числа сильных ссылок?

    Не следует смешивать логические этапы. Когда объект становится доступным для уничтожения, runtime выполняет необходимую процедуру разрушения и обнуления weak-ссылок; точный момент относительно оптимизаций, deinit и внутренних операций не следует выводить из исходной области видимости переменной. Гарантируется безопасная семантика weak-ссылки, а не конкретная трассировка retain/release.

  2. Гарантирует ли weak-ссылка, что объект останется жив во время последующего использования результата чтения?

    При чтении weak-ссылки runtime получает временное сильное значение. Это позволяет безопасно использовать полученный результат в рамках соответствующего выражения или временного удержания. Но отдельные последовательные чтения weak-ссылки могут дать разные результаты: между ними объект способен быть уничтожен.

  3. Можно ли заменить weak на unowned только ради уменьшения накладных расходов?

    Только если жизненный цикл гарантирован: объект, на который ссылаются, обязан жить не меньше, чем каждая unowned-ссылка. unowned не участвует в удержании объекта и не обнуляется при его уничтожении, поэтому нарушение гарантии превращается в runtime-ошибку. Если такая гарантия не доказуема, безопаснее использовать weak и обработать возможное отсутствие объекта.