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