Каков компромисс у unowned(unsafe) по сравнению с обычной unowned ссылкой в Swift?
unowned(unsafe) не владеет объектом, но в отличие от обычной unowned ссылки не проверяет, существует ли объект при обращении. Это устраняет runtime-проверку, но обращение после уничтожения объекта приводит к неопределённому поведению вместо гарантированного безопасного сбоя.
В Swift ARC автоматически управляет временем жизни объектов, но иногда объекту нужна ссылка на владельца без увеличения счётчика сильных ссылок. Для этого существуют weak и unowned ссылки: они предотвращают циклы владения, сохраняя при этом контроль над отношениями между объектами.
Обычная unowned ссылка сохраняет предположение о гарантированно более долгой жизни объекта, но проверяет это предположение во время выполнения. unowned(unsafe) предоставляет более низкоуровневый вариант без такой проверки.
Если объект, на который указывает unowned(unsafe), уничтожен раньше ссылки, сама ссылка не становится nil и не превращается в безопасный маркер отсутствия. Она остаётся висячей ссылкой.
Неверное использование может привести к обращению к освобождённой памяти, повреждению состояния или аварийному завершению. Ошибка может проявиться не в месте нарушения жизненного цикла, поэтому её сложнее диагностировать.
Обычная unowned ссылка не удерживает объект, но при чтении проверяет, что объект ещё жив. Если объект уже уничтожен, Swift останавливает выполнение с ошибкой времени выполнения.
unowned(unsafe) также не удерживает объект, однако отключает эту проверку. Поэтому разработчик самостоятельно обязан гарантировать, что ссылка никогда не будет прочитана после уничтожения объекта.
Минимальный пример безопасного по времени жизни использования выглядит так:
Пока parent гарантированно жив, обращение к child.parent допустимо. Если parent уничтожить, сохранив child, последующее чтение child.parent уже не имеет безопасной семантики.
Практическое преимущество unowned(unsafe) — отсутствие runtime-проверки и возможность использовать его в низкоуровневом коде, где жизненный цикл строго контролируется. Компромисс — потеря защитного механизма Swift; это не оптимизация, которую следует применять без измеримой причины и формально доказанной гарантии времени жизни.
Предположим, граф объектов создаётся специализированным менеджером: менеджер владеет всеми узлами, а каждый узел хранит ссылку на менеджер. Если менеджер гарантированно живёт дольше каждого узла, возможны три варианта.
Сильная ссылка проста, но может создать цикл владения и задержать освобождение объектов. Weak автоматически становится nil, однако требует Optional-проверок и подходит, когда объект действительно может исчезнуть.
Обычная unowned устраняет Optional, но завершает выполнение с ошибкой при нарушении гарантии. unowned(unsafe) убирает и эту проверку, поэтому выбран только при наличии внешнего инварианта: менеджер уничтожается строго после всех узлов, а архитектура не допускает обратного порядка.
В обычном прикладном коде предпочтительнее weak или обычная unowned. Результат выбора unowned(unsafe) оправдан лишь тогда, когда жизненный цикл проверен архитектурой или внешним протоколом, а отказ от runtime-защиты действительно необходим.
unowned(unsafe) в nil после уничтожения объекта?Нет. В отличие от weak, такая ссылка не является автоматически обнуляемой. После уничтожения объекта она указывает на недействительное место, поэтому чтение не даёт корректного Optional-значения.
unowned и unowned(unsafe)?Для обычной unowned нарушение приводит к диагностируемой ошибке времени выполнения при обращении к ссылке. Для unowned(unsafe) runtime-проверки нет, поэтому результатом может стать неопределённое поведение: от немедленного сбоя до повреждения данных.
unowned(unsafe) weak, если не хочется писать проверки на nil?Нет. weak выражает ситуацию, в которой объект может исчезнуть, и безопасно сообщает об этом через nil. unowned(unsafe) выражает противоположный контракт: объект обязан жить достаточно долго; выбор этого типа вместо weak только скрывает ошибку жизненного цикла и делает её опаснее.