Две фоновые задачи одновременно читают и переназначают одну сильную ссылочную переменную. Достаточно ли ARC для безопасности такой операции?
Нет. ARC управляет временем жизни объектов, но не синхронизирует доступ к самой переменной. Одновременное чтение и переназначение общей сильной ссылки без синхронизации создаёт гонку данных, даже если операции retain/release корректно поддерживают время жизни экземпляров.
ARC автоматизировал ручное управление подсчёком ссылок в Objective-C и Swift: компилятор добавляет необходимые операции удержания и освобождения объектов. Его задача — определить, когда экземпляр больше не удерживается сильными ссылками, а не согласовать доступ нескольких потоков к общей памяти.
Проблема потоковой безопасности относится к более высокому уровню. Для неё применяются блокировки, последовательные очереди или акторы; сам ARC не заменяет эти механизмы.
Сильная переменная состоит не только из ссылки на объект, но и из операции доступа к хранилищу этой переменной. Если один поток читает её, пока другой присваивает новое значение или nil, действия могут пересекаться.
ARC не допускает преждевременного уничтожения объекта из-за корректного освобождения независимых сильных ссылок, но это не делает совместное хранилище ссылок безопасным. Кроме того, одновременный доступ может привести к гонке данных, нарушению инвариантов программы и неопределённым результатам.
Нужно различать два уровня:
Безопасный вариант — защищать общее хранилище блокировкой или изолировать его актором:
Локальная константа current удерживает прочитанный объект сильной ссылкой на время использования, если тип и контекст требуют такого владения. Однако блокировка защищает не только ARC-ссылку, но и логическую последовательность действий; в реальном коде важно не выполнять под блокировкой долгие или повторно входящие операции.
Актор является предпочтительным решением в коде на Swift Concurrency, когда состояние можно изолировать внутри него. NSLock уместен для низкоуровневого синхронного кода, но требует строгого соблюдения правил захвата и освобождения блокировки.
Сервис хранит текущий экран приложения в общей сильной переменной. Один поток устанавливает новый экран, пока другой проверяет старый и вызывает его метод. Простое присваивание без синхронизации может пересечься с чтением; ARC при этом не гарантирует согласованность этой операции.
Вариант с одной глобальной сильной ссылкой без защиты прост, но небезопасен. Вариант с блокировкой обеспечивает синхронный доступ, однако требует контроля взаимных блокировок и времени удержания lock. Вариант с актором лучше выражает владение состоянием и интегрируется со Swift Concurrency, но требует асинхронного взаимодействия и изоляции вызывающего кода.
Практический выбор — актор для прикладного состояния или последовательная очередь для существующей архитектуры; блокировка оправдана в небольшом синхронном компоненте. Результат: ARC отвечает за корректное освобождение объектов, а выбранный механизм синхронизации — за безопасный доступ к общей ссылке.
Да, корректные операции управления владением должны не допустить уничтожения объекта до освобождения последней сильной ссылки. При параллельном освобождении разных уже существующих сильных ссылок deinit должен выполниться один раз после последнего освобождения.
Это не распространяется на гонку при доступе к одной и той же переменной. Нельзя делать вывод, что безопасное внутреннее обновление счётчика ссылок превращает произвольную программу с общими переменными в потокобезопасную.
Локальная сильная копия обычно удерживает объект во время дальнейшего использования, поэтому объект не исчезнет из-за освобождения других владельцев после успешного чтения. Но само чтение общей переменной должно быть синхронизировано, если другой поток одновременно её изменяет.
Иными словами, локальная копия решает задачу времени жизни уже полученного объекта, но не решает гонку на этапе получения ссылки. Для этого всё равно нужны lock, актор или другой согласованный механизм.
let, чтобы несколько потоков могли безопасно использовать объект?Нет. Неизменяемость самой ссылки означает, что её нельзя переназначить через данный идентификатор, но не делает автоматически потокобезопасным состояние объекта. Методы экземпляра могут изменять его свойства, а несколько потоков могут одновременно обращаться к этим свойствам.
let может устранить гонку на переназначение конкретной ссылки, но не заменяет синхронизацию изменяемого состояния объекта. Потокобезопасность должна быть обеспечена самим типом — например, через actor, lock или другой протокол доступа.