Если одно замыкание присвоить двум переменным, создаёт ли это независимые копии захваченного изменяемого состояния?
Нет. Присваивание замыкания двум переменным не создаёт независимые копии его захваченного изменяемого состояния: обе переменные вызывают одно и то же захваченное хранилище. Поэтому изменение состояния через одну переменную будет видно через другую.
Замыкания в Swift предназначены для передачи поведения вместе с окружением, в котором это поведение было создано. Захват локальных переменных позволяет функции продолжать работать с этим окружением даже после завершения исходной области видимости.
Для изменяемых захваченных переменных Swift использует общее внутреннее хранилище, а не отдельную копию состояния при каждом присваивании замыкания. Это сохраняет ожидаемую семантику захвата состояния, но требует учитывать совместный доступ к нему.
Копирование значения замыкания может выглядеть как создание независимых экземпляров функции. Однако копируются сами ссылки на поведение и захваченное окружение, поэтому несколько копий замыкания могут совместно изменять одно состояние.
Ошибка особенно опасна, если такое состояние используется из разных потоков: обычный захват переменной не обеспечивает синхронизацию и может привести к гонке данных. Для независимого состояния нужно явно создать разные замыкания с разными захваченными хранилищами.
Рассмотрим механизм на минимальном примере:
Функция makeCounter создаёт одно замыкание и одно внутреннее хранилище для value. После присваивания second = first обе переменные ссылаются на одно замыкание и его захваченное хранилище, поэтому второй вызов продолжает счёт с результата первого.
Это не означает, что всякий захваченный объект автоматически копируется или что замыкание всегда ведёт себя как класс. Важно различать копирование самого значения замыкания и копирование его окружения: значение замыкания копируется, а изменяемое захваченное состояние остаётся общим.
Если захвачен объект-класс, общий доступ объясняется ссылочной семантикой самого объекта. Если захвачена изменяемая локальная переменная-значение, Swift помещает её в общее внутреннее хранилище, чтобы замыкание могло изменять её после выхода из исходной области видимости.
Независимое состояние получается при повторном вызове фабрики, а не при копировании уже созданного замыкания. Потокобезопасность также не появляется автоматически: для общего состояния нужны подходящие средства синхронизации, например актор или другая явно выбранная модель изоляции.
Сервис создаёт обработчик события, а затем передаёт его в два компонента. Разработчик копирует обработчик, рассчитывая, что каждый компонент получит собственный счётчик, но оба компонента начинают изменять один и тот же счётчик.
Можно создать копию уже существующего замыкания, но это не решит проблему: захваченное хранилище останется общим. Можно захватывать только неизменяемый снимок значения, однако тогда обработчик не сможет совместно обновлять счётчик.
Корректное решение — вызвать фабрику дважды и передать каждому компоненту отдельное замыкание. Это создаёт два независимых захваченных хранилища. Если же счётчик должен быть общим, состояние следует выделить в отдельный объект или актор и явно определить правила синхронизации.
Список захвата фиксирует значение в момент создания замыкания. Последующие изменения исходной переменной не изменят зафиксированное значение, но копии самого замыкания всё равно будут обращаться к одному захваченному значению внутри общего окружения.
Да, если захватывается значение структуры через список захвата: замыкание получает снимок значения, и изменения исходной переменной после создания замыкания на него не влияют. Но если несколько переменных указывают на одно замыкание, они всё равно совместно используют его захваченное хранилище; независимость исходной переменной от замыкания не равна независимости копий замыкания друг от друга.
Нет, совместное захваченное изменяемое состояние само по себе не защищено от одновременного доступа. Параллельные чтения и записи могут привести к гонке данных, даже если логика замыкания кажется простой. Нужно изолировать состояние, например поместить его в actor, либо применять иной механизм синхронизации, подходящий для конкретной архитектуры.