Можно ли безопасно передать ссылку на actor между задачами Swift, несмотря на его ссылочную семантику?
Да. Тип actor неявно удовлетворяет требованиям Sendable, поэтому ссылку на один и тот же actor можно передавать между конкурентными задачами. Безопасность обеспечивается не копированием объекта, а тем, что доступ к его изолированному изменяемому состоянию проходит через actor.
Акторы появились как средство безопасной работы с разделяемым изменяемым состоянием без ручного управления блокировками. В традиционной многопоточности ссылка на общий объект сама по себе не предотвращает одновременную запись и чтение его полей.
Модель Swift Concurrency разделяет владение ссылкой и доступ к состоянию. Ссылку на actor можно разделять, а операции над его изолированным состоянием выполняются через механизм actor-изоляции.
Ссылочная семантика означает, что разные задачи получают доступ к одному экземпляру, а не к независимым копиям. Если бы они могли напрямую менять его поля, возникли бы гонки данных и нарушение инвариантов.
Actor решает проблему только для своего изолированного состояния. Передача ссылки безопасна, но это не делает автоматически безопасными любые объекты, которые actor возвращает наружу, и не защищает произвольный код в его nonisolated-методах.
Ссылка на actor является безопасно передаваемым значением: actor-типы неявно соответствуют Sendable. При передаче между задачами перемещается доступ к идентичности actor, а не копия его внутренних полей.
Когда задача вызывает изолированный метод actor, Swift проверяет границу изоляции. Вызов обычно требует await, если он выполняется вне самого actor, потому что задача может приостановиться до получения возможности выполнить операцию на actor.
Сам actor последовательно обслуживает изолированные участки своего состояния. Поэтому две задачи могут совместно использовать один actor, но не получают прямого конкурентного доступа к его изолированным свойствам.
В примере ссылка counter безопасно захватывается detached-задачей, а изменение value выполняется через изолированный метод. Однако actor не распространяет свою защиту на ссылочный объект, возвращённый методом, если этот объект затем изменяется вызывающим кодом.
Компромисс состоит в том, что actor упрощает защиту состояния, но добавляет асинхронные границы и возможные приостановки. Для небольших независимых значений иногда лучше использовать неизменяемые Sendable-значения или копирование, а actor выбирать для действительно разделяемого состояния.
Сервис кэширования используется несколькими сетевыми задачами. Вариант с обычным классом и ручным NSLock может быть быстрым, но требует дисциплины: каждый доступ должен блокироваться, а сложные цепочки вызовов повышают риск взаимной блокировки.
Вариант с копированием всего кэша устраняет гонки, но плохо масштабируется по памяти и может приводить к устаревшим снимкам данных. Вариант с actor позволяет передать один экземпляр всем задачам и оставить внутренние структуры кэша непередаваемыми.
Практический выбор — actor, если состояние действительно разделяется и операции над ним должны быть централизованы. Методы actor должны возвращать безопасные значения или явно продуманные снимки, а не выдавать наружу внутренние изменяемые объекты.
Нет. Все задачи получают ссылку на тот же экземпляр actor. Это позволяет им совместно работать с одним состоянием, но само состояние остаётся защищённым actor-изоляцией.
Следовательно, передача ссылки не означает передачу снимка данных. Если нужен независимый набор данных, его необходимо явно скопировать в подходящее значение.
Task.detached?Task.detached не наследует изоляцию, task-local значения и контекст родительской задачи, поэтому к захватам предъявляются строгие требования. Ссылка на actor допустима, поскольку actor-тип неявно является Sendable.
Это разрешает передать саму ссылку, но не даёт detached-задаче права читать внутренние свойства напрямую. Для работы с состоянием она всё равно должна обращаться к изолированным методам actor через границу concurrency.
Да, если это значение остаётся внутри изоляции actor. Например, actor может владеть внутренней структурой, которую нельзя безопасно передавать между задачами, пока наружу не выдаётся сама структура или ссылка на неё.
Ограничения появляются на границе actor: параметры и результаты операций, пересекающих конкурентный контекст, должны удовлетворять требованиям безопасной передачи. Поэтому обычно наружу возвращают копию, Sendable-значение или специально подготовленный снимок, а не внутренний объект.