Разберите замену сильной ссылочной переменной через inout: когда ARC освобождает прежний объект?
inout не передаёт право владения объектом и не продлевает его жизнь после вызова. При замене значения сильной переменной прежняя сильная ссылка прекращается; если других сильных ссылок нет, старый объект становится доступен для уничтожения.
Для inout точный физический момент освобождения может зависеть от механизма copy-in/copy-out и оптимизаций ARC: замена может быть применена непосредственно во время вызова или при обратной записи после него. Поэтому на точный порядок deinit относительно соседних операций нельзя опираться как на контракт.
inout появился как механизм изменения переменной вызывающего кода внутри функции, сохраняя модель значимых типов Swift. Он решает задачу передачи значения для временного эксклюзивного доступа с последующей записью результата обратно.
В ссылочных типах переменная хранит ссылку на экземпляр, поэтому ARC должен корректно обработать старое и новое значение этой переменной. При этом inout остаётся механизмом доступа к хранилищу, а не специальным видом владения объектом.
Рассмотрим функцию, которая заменяет переданную ссылку новым экземпляром. В момент замены старый объект может потерять последнюю сильную ссылку, хотя вызов функции ещё не завершён.
Ошибочно считать, что inout автоматически сохраняет старый объект до конца функции или, наоборот, что deinit обязательно выполняется строго в строке присваивания. На наблюдаемый момент влияют другие ссылки, временные значения и оптимизация ARC.
Сильная переменная владеет объектом через хранящуюся в ней ссылку. Когда в это хранилище записывается другая ссылка, старая ссылка освобождается, а новая начинает участвовать в подсчёте сильных ссылок.
Если старый объект больше никем сильно не удерживается, после уменьшения счётчика он становится доступен для уничтожения, и ARC может запустить его deinit. Если существуют другие сильные ссылки, объект продолжит жить независимо от inout.
inout обеспечивает эксклюзивный доступ к переменной на время операции. Семантически значение может быть скопировано во временное хранилище, изменено внутри функции и записано обратно после завершения вызова. Реализация вправе оптимизировать такой доступ, поэтому нельзя использовать inout как способ точно определить момент вызова deinit.
При замене старая ссылка на old прекращает владение им. Если других сильных ссылок нет, old может быть уничтожен в ходе замены или при записи результата inout; полагаться на строгое расположение сообщения deinit относительно function finished не следует.
Объект new после успешной записи удерживается сильной переменной item. Если функция присвоит nil, новый объект также станет доступен для уничтожения, когда исчезнет последняя сильная ссылка на него.
В методе обновления состояния передают через inout сильную ссылку на кэшируемый ресурс. Функция создаёт новый ресурс и заменяет старый. Разработчик ожидает, что старый ресурс будет жить до конца метода, потому что исходная переменная всё ещё находится в области видимости.
Вариант с дополнительной сильной локальной ссылкой действительно может явно продлить жизнь старого объекта до последнего использования этой локальной ссылки. Минус — усложнение владения и риск скрыть реальную причину зависимости от времени жизни.
Вариант с weak не подходит для гарантии: объект может исчезнуть до замены, если другие владельцы отпустят его. Правильное решение — не полагаться на момент deinit, а явно завершать внешний ресурс отдельным методом, если порядок освобождения важен; inout использовать только для атомарной замены значения в пределах эксклюзивного доступа.
Дополнительный вопрос 1: Может ли старый объект быть уничтожен внутри функции до её завершения?
Да, если изменение inout-значения непосредственно заменяет последнюю сильную ссылку на старый объект. Но при copy-in/copy-out старое значение может оставаться во внешнем хранилище до обратной записи. Поэтому корректный вывод — объект может стать доступным для уничтожения при замене или writeback, а не гарантированно в одной фиксированной строке.
Дополнительный вопрос 2: Продлевает ли inout жизнь объекта после возврата из функции?
Нет. После завершения доступа inout остаётся только результат, записанный в исходную переменную. Если в ней находится nil или другая ссылка, прежний объект не получает дополнительного владельца из-за самого факта передачи через inout.
Дополнительный вопрос 3: Что изменится, если на старый объект есть ещё одна сильная ссылка?
Замена inout-переменной уменьшит число сильных ссылок только на единицу. Пока дополнительная ссылка существует, старый объект не уничтожается и его deinit не вызывается. Он станет доступен для уничтожения лишь после освобождения последней оставшейся сильной ссылки.