При создании замыкания, выходящего за пределы метода, чем отличается захват свойства от захвата экземпляра для времени жизни владельца?
Захват свойства удерживает захваченное значение, но сам по себе не обязан удерживать экземпляр-владелец. Захват экземпляра удерживает весь экземпляр, поэтому его время жизни продлевается до уничтожения замыкания или прекращения его хранения.
Если свойство содержит ссылку на объект, захват свойства всё равно может продлить жизнь этого объекта. Однако владелец свойства при этом не удерживается автоматически, если только захваченное значение не ссылается на него обратно.
Замыкания могут сохраняться после возврата метода: например, их помещают в очередь, таймер или свойство другого объекта. Поэтому Swift должен сохранять захваченные значения в специальном контексте замыкания, а ARC управляет временем жизни ссылочных объектов в этом контексте.
Такой подход избавляет разработчика от ручного управления retain и release, но требует явно понимать, что именно захватывается. Автоматический подсчёт ссылок не определяет намерение разработчика и не устраняет циклы владения сам по себе.
Если замыканию нужна только крупная зависимость или значение свойства, захват всего владельца может неоправданно продлить жизнь контроллера, модели или другого объекта. Это увеличивает потребление памяти и может удерживать связанные с владельцем ресурсы.
Обратная ошибка тоже возможна: захват только свойства может преждевременно уничтожить владельца, если замыкание позже рассчитывает обратиться к его состоянию или методам. Неверный выбор приводит либо к утечке через цикл, либо к отсутствию ожидаемого объекта во время выполнения.
При захвате экземпляра замыкание хранит сильную ссылку на него, если не указано иное. Поэтому выражение, обращающееся к свойству через экземпляр внутри замыкания, обычно приводит к захвату экземпляра целиком.
При захвате отдельного свойства в контекст замыкания помещается результат вычисления этого свойства. Для ссылочного свойства это сильная ссылка на значение свойства, но не дополнительная сильная ссылка на объект-владелец:
В первом методе замыкание удерживает Resource, но Owner может быть уничтожен после исчезновения других сильных ссылок на него. Во втором методе замыкание удерживает Owner, а через него доступен и Resource.
Важна также точка фиксации значения. Если свойство захватывается как отдельное значение, обычно сохраняется его состояние на момент создания замыкания; последующая замена свойства владельца не изменяет уже захваченное значение. При захвате владельца замыкание при каждом вызове обращается к текущему состоянию экземпляра.
Для значения-структуры захватывается значение, а не ссылка на владелец. Для ссылочного свойства захватывается ссылка на объект свойства. Если этот объект сам содержит сильную ссылку обратно на владельца, цикл всё равно возможен косвенно.
Компромисс выбирают по ответственности замыкания. Если обработчик должен жить независимо от владельца и ему достаточно зависимости, захват отдельного свойства уменьшает удерживаемый граф объектов. Если обработчик является частью поведения владельца, часто нужен слабый захват владельца с проверкой его существования, а не захват свойства любой ценой.
Сервис очереди хранит замыкание обработчика, а обработчик использует только кэш владельца. Вариант с захватом владельца прост, но очередь может удерживать владельца вместе с большим графом его зависимостей; при обратной ссылке из владельца на очередь возникает цикл. Вариант со слабым захватом владельца предотвращает цикл, но обработчик перестаёт работать после уничтожения владельца.
Третий вариант — захватить сам кэш. Он не удерживает владельца и позволяет обработчику завершить работу независимо от его жизненного цикла. Его выбирают, если кэш действительно является самостоятельной зависимостью, а обработчику не нужны методы, настройки или другое состояние владельца.
Результат — владелец освобождается вовремя, а кэш живёт ровно столько, сколько требуется очереди. Если кэш содержит обратную ссылку на владельца, этот вариант дополнительно проверяют: иначе цикл может сохраниться уже между захваченным кэшем и владельцем.
1. Меняется ли захваченное свойство, когда владелец позже получает другое значение?
Если захвачено отдельное значение свойства, замыкание хранит его снимок на момент создания. Последующее присваивание новому объекту в свойство владельца не заменяет значение внутри контекста замыкания.
Исключение связано не с ARC, а с захватом общей изменяемой переменной или контейнера состояния. В таком случае замыкание может видеть изменения этого общего хранилища, поэтому важно различать захват значения и захват изменяемого контекста.
2. Может ли захват свойства всё равно создать цикл ссылок?
Да. ARC анализирует конкретные ссылки, а не синтаксическое имя захваченного элемента. Если захваченный объект свойства сильно ссылается на владельца, а владелец прямо или косвенно удерживает замыкание, возникает цикл через захваченный объект.
Поэтому замена захвата владельца на захват свойства не является универсальным способом устранения утечек. Нужно проследить весь граф владения и ослабить одну ссылку, которая не должна быть владеющей.
3. Что произойдёт, если замыкание захватит свойство-значение, но затем ему понадобятся методы владельца?
Само захваченное свойство не предоставляет доступ к владельцу. Если обработчику позже нужны методы или другие свойства владельца, придётся захватить владельца отдельно либо изменить архитектуру и передать замыканию специализированную зависимость.
Сильный захват владельца проще, но может продлить его жизнь или создать цикл. Слабый захват не создаёт такого удержания, однако владелец может исчезнуть до вызова, поэтому обработчик должен корректно обработать отсутствие объекта.