При захвате изменяемой локальной переменной в escaping-замыкании что определяет видит ли замыкание её последующие изменения?
Для изменяемой локальной переменной escaping-замыкание обычно захватывает не отдельную копию значения, а общее скрытое хранилище. Поэтому замыкание видит последующие изменения переменной, а изменения через замыкание видны исходному коду. Явный список захвата, например с захватом значения, меняет это поведение: в замыкание попадает снимок значения на момент создания.
Замыкания нужны для передачи поведения вместе с окружением, в котором это поведение было создано. Без захвата окружения функция обратного вызова не смогла бы использовать локальные параметры и переменные после завершения исходной функции.
В Swift время жизни захваченного хранилища автоматически продлевается, если оно нужно escaping-замыканию. Это позволяет безопасно работать с асинхронными операциями, но требует понимать, разделяется ли хранилище или создаётся его копия.
Локальная переменная обычно ассоциируется с конкретным вызовом функции. Однако escaping-замыкание может выполниться позже, когда этот вызов уже завершён. Если разработчик ожидает независимую копию, но получает общее хранилище, последующие изменения могут повлиять на результат обратного вызова.
Обратная ошибка тоже опасна: ожидание общего состояния при использовании списка захвата приводит к устаревшему снимку. Особенно заметно это в обработчиках событий, таймерах и асинхронных сетевых операциях.
При захвате изменяемой локальной переменной Swift размещает её в скрытом общем хранилище, часто называемом box. Исходный код и замыкание обращаются к одному этому хранилищу, поэтому значение не копируется заново при каждом вызове замыкания.
После выхода из makeCounter переменная value продолжает существовать, потому что на её хранилище ссылается возвращённое замыкание. Каждый вызов изменяет то же состояние.
Это не означает, что любое захваченное значение всегда является общей ссылкой. Список захвата позволяет явно захватить текущее значение:
Здесь number внутри замыкания — отдельное захваченное значение, не связанное с последующим изменением внешней переменной. Если захватывается ссылочный объект, копируется ссылка на объект, поэтому его внутреннее состояние всё ещё может быть общим.
Семантика захвата не отменяет value semantics и reference semantics. Для захваченной изменяемой локальной переменной ключевым является общее захваченное хранилище; для значения внутри этого хранилища действуют обычные правила типа, включая копирование структуры и Copy-on-Write.
Сервис запускает асинхронную операцию и формирует обработчик результата. В обработчике нужно использовать идентификатор запроса, актуальный на момент запуска, а не значение, которое переменная может получить позже.
Вариант с обычным захватом изменяемой переменной сохраняет общее хранилище. Его плюс — обработчик видит актуальное состояние; минус — результат зависит от времени выполнения и возможных последующих изменений.
Вариант со списком захвата значения создаёт снимок. Его плюс — детерминированность и отсутствие зависимости от дальнейших изменений; минус — обработчик не увидит намеренно обновлённое значение.
Обычно для идентификатора конкретного запроса выбирают снимок через список захвата, а для счётчика прогресса — общее захваченное состояние или специально выделенный потокобезопасный объект. Это явно выражает ожидаемую модель данных и снижает риск гонок и использования устаревших значений.
Да. Если escaping-замыкание продолжает существовать, оно удерживает необходимое хранилище. Поэтому локальная переменная не уничтожается вместе с активацией функции. Когда замыкание и другие владельцы хранилища исчезают, оно освобождается по обычным правилам управления памятью.
[value] означает глубокую копию?Нет. Для структуры захватывается значение структуры, но её ссылочные свойства продолжают указывать на те же объекты. Для экземпляра класса захватывается ссылка на объект, а не независимая копия объекта. Глубокая копия возможна только если её явно реализует сам тип или прикладной код.
Нет. Оно обеспечивает доступ к одному состоянию, но не делает чтение и изменение атомарными и не гарантирует отсутствие гонок. При обращении из нескольких потоков нужны подходящие средства синхронизации, например актор, сериализация доступа или другой потокобезопасный механизм.