При копировании замыкания с сильным захватом объекта что удерживает объект после обнуления исходной ссылки?
Объект продолжает жить, пока существует хотя бы одна копия замыкания, содержащая его сильный захват. Обнуление исходной переменной не освобождает объект: сильная ссылка находится в контексте захвата замыкания.
Копирование замыкания не создаёт копию самого экземпляра класса. Все копии обращаются к тому же объекту, а точное количество внутренних retain-операций является деталью реализации и не должно использоваться как контракт.
ARC был создан, чтобы автоматизировать управление сильными ссылками вместо ручного согласования операций удержания и освобождения объектов. Особенно важно это для замыканий: они могут пережить функцию, в которой были созданы, поэтому захваченное состояние должно храниться отдельно от её стека.
Для этого Swift формирует контекст замыкания. В нём находятся захваченные значения, включая сильные ссылки на экземпляры классов. ARC управляет временем жизни этого контекста так же, как и других ссылочных объектов.
Представим, что локальная переменная содержит объект, затем замыкание сохраняется в другом месте, а исходная переменная обнуляется. Если ошибочно считать, что объект зависит только от этой переменной, можно ожидать преждевременный вызов deinit.
На практике это приводит к другой ошибке: разработчик может удалить замыкание, считая его безобидной копией, хотя последняя копия фактически владеет объектом. Обратная проблема возникает при слабом захвате: копирование такого замыкания объект не удерживает.
При создании замыкания с сильным захватом в его контексте сохраняется сильная ссылка на текущее значение. Когда замыкание копируется, копируется значение замыкания или ссылка на его внутреннее хранилище; семантически каждая доступная копия сохраняет возможность использовать захваченный объект.
Объект становится доступным для уничтожения только после исчезновения всех сильных владельцев, включая последнюю копию замыкания. Поэтому нужно рассуждать по графу владения: исходная переменная — один владелец, контекст замыкания — другой.
В примере список захвата [session] сохраняет текущее значение session. После обнуления исходной переменной объект всё ещё удерживается замыканиями. Когда first и second обнулены, сильных ссылок на объект больше нет.
Это не означает, что Swift обязан выполнить отдельный retain для каждой копии или немедленно вызвать deinit в конкретной строке: оптимизатор может менять внутреннюю реализацию, сохраняя наблюдаемую семантику времени жизни. Надёжный вывод — объект не может быть уничтожен, пока существует сильный захват, необходимый живой копии замыкания.
Сервис создаётся при открытии экрана, а callback передаётся нескольким компонентам. Исходная переменная сервиса затем обнуляется при закрытии экрана, но один из компонентов продолжает хранить callback.
Варианты решения:
nil.Если callback должен работать только при наличии сервиса и не является его владельцем, выбирают слабый захват. Если callback обязан гарантированно завершить операцию с этим сервисом, допустим сильный захват, но граф владения нужно проверить отдельно на цикл.
Создаёт ли копирование замыкания отдельный экземпляр захваченного класса?
Нет. Копируется значение замыкания и его контекст, но экземпляр класса остаётся тем же объектом. Две копии могут независимо вызывать методы одного экземпляра; изменение его свойств будет видно через обе копии.
Может ли объект быть уничтожен, если одна копия замыкания всё ещё существует, но никогда не будет вызвана?
Не при обычном сильном захвате. Для ARC важно наличие сильной ссылки, а не будущий факт вызова. Даже невызванное замыкание удерживает захваченный объект, пока само замыкание не освобождено или не заменено.
Что изменится при копировании замыкания со слабым захватом?
Копии продолжат ссылаться на тот же объект через weak-ссылку, но не станут его владельцами. Если все сильные ссылки исчезнут, объект будет уничтожен, а последующее чтение слабой ссылки даст nil. Копирование слабого замыкания не продлевает время жизни объекта, но каждая копия всё равно должна быть безопасна к выполнению после его уничтожения.