Служит ли weak-ссылка синхронизацией при одновременном уничтожении объекта в другом потоке?
Нет. weak-ссылка автоматически обнуляется при уничтожении объекта, но не обеспечивает синхронизацию доступа между потоками. После успешного чтения weak-ссылки объект обычно удерживается временной сильной ссылкой, однако сама проверка weak-ссылки и последующее обращение к ней не образуют атомарную операцию.
ARC решает задачу управления временем жизни объектов через сильные ссылки, а weak-ссылки — задачу безопасного наблюдения за объектом без продления его жизни. Их автоматическое обнуление предотвращает обращение к уже уничтоженному объекту, но механизм владения не заменяет блокировки, акторы или другие средства синхронизации.
Пусть один поток уничтожает последний сильный владеющий объект, а другой одновременно читает weak-ссылку. Нельзя считать, что проверка ссылки, вызов метода и изменение самой ссылки выполняются как единая неделимая операция.
Особенно опасен шаблон «сначала проверить weak-ссылку, затем снова обратиться к ней»: между этими действиями объект может быть уничтожен, а повторное чтение уже вернёт nil. Одновременная запись в то же хранилище weak-ссылки также требует синхронизации.
При чтении weak-ссылки runtime пытается получить временную сильную ссылку. Если объект ещё существует, успешное чтение удерживает его живым на время использования этой локальной ссылки; если объект уже уничтожен, результатом будет nil.
Правильный шаблон — прочитать weak-ссылку один раз в локальную сильную переменную и использовать только её:
Блокировка защищает доступ к общему хранилищу weak-ссылки, а локальная переменная current становится сильной ссылкой и сохраняет делегат живым во время handle(). Вместо блокировки можно использовать изоляцию актора, последовательную очередь или другую согласованную модель владения.
Важно различать два свойства: zeroing weak защищает от висячей ссылки, а синхронизация защищает от гонок данных и несогласованных последовательностей операций. Даже если runtime корректно обнуляет weak-ссылку, это не делает произвольный составной алгоритм потокобезопасным.
В приложении Registry хранит делегат контроллера слабой ссылкой, чтобы не создавать цикл владения. Фоновый поток иногда вызывает notify(), пока поток интерфейса закрывает экран и тем самым уничтожает контроллер.
Вариант с прямым чтением weak-ссылки без синхронизации прост, но не задаёт корректных гарантий при одновременной записи или чтении. Вариант с сильной ссылкой в реестре устраняет гонку времени жизни, но меняет владение: контроллер больше не сможет освободиться только из-за закрытия экрана.
Выбранный вариант — защищать weak-хранилище блокировкой, затем сохранять результат чтения в локальную сильную ссылку. Так реестр не владеет контроллером постоянно, а уже начавшийся вызов гарантированно завершает работу с экземпляром; после выхода из notify() локальная сильная ссылка освобождается.
Достаточно ли защитить только запись weak-ссылки блокировкой?
Нет. Если чтение происходит без той же синхронизации, оно всё ещё конкурирует с записью и не образует согласованного доступа к общему хранилищу. Все операции, участвующие в протоколе доступа к этой переменной, должны использовать одну модель синхронизации.
Безопасно ли вызвать метод после одного чтения weak-ссылки?
Да, если результат сразу сохранён в локальную сильную ссылку и вызов выполняется через неё. Уничтожение объекта в другом потоке после успешного получения этой локальной ссылки не прервёт его время жизни до освобождения локальной сильной ссылки.
Гарантирует ли weak-ссылка, что параллельный читатель увидит либо объект, либо nil без дополнительных условий?
Для отдельного чтения runtime обеспечивает корректную семантику zeroing weak: ссылка не должна оставаться указателем на уже уничтоженный объект. Но это не гарантирует согласованность нескольких чтений, порядок операций или отсутствие гонки при изменении самой переменной. Для таких гарантий необходимы блокировки, акторы или иная синхронизация.