Swift Concurrency продолжает эволюционировать, и одно из ключевых изменений - пересмотр логики работы nonisolated асинхронных функций. Сейчас они ведут себя противоречиво: синхронные nonisolated методы выполняются в контексте вызывающей стороны (например на акторе), а асинхронные - всегда переключаются на внешний executor, что вызывает неожиданные ошибки типов и усложняет архитектуру.
Суть проблемы - разрыв между ожиданием и реальностью:
Когда вы вызываете nonisolated асинхронный метод из актора, компилятор вынужден применять строгие Sendable-проверки ко всем аргументам, даже если метод по своей природе не должен покидать контекст актора. Это приводит к ложным ошибкам, особенно в библиотечном коде, где автор API может не предусмотреть такой сценарий. Даже стандартная библиотека Swift Concurrency сталкивалась с этой проблемой.
Решение - явное управление изоляцией через новые модификаторы:
Предложение SE-0461 вводит два новых инструмента для точного контроля:
🔵nonisolated(nonsending) - явно указывает, что асинхронная функция должна выполняться в контексте вызывающего актора (унаследовать его изоляцию). Это поведение становится предсказуемым и аналогичным синхронным функциям.
🔵
@concurrent - явно указывает, что функция должна переключаться на внешний executor (это старое поведение по умолчанию). Используется в случаях, когда необходимо гарантированно выйти из текущего актора для выполнения работы, например, для избежания его блокировки или для фоновых операций.class NotSendable {
nonisolated(nonsending)
func performAsync() async {
// Работает в контексте вызывающего актора
}
}
actor MyActor {
let item = NotSendable()
func call() async {
item.performSync() // OK
await item.performAsync() // Теперь тоже OK, ошибки типов нет!
}
}Макрос
#isolation и работа с замыканиями:Для продвинутых сценариев, таких как создание оберток (например withResource), расширяется функциональность макроса
#isolation. Теперь он может корректно передавать контекст изоляции в nonisolated(nonsending) функции, что делает написание высокоуровневых асинхронных API значительно проще и безопаснее.Важное уточнение: неструктурированные задачи (Task { }), созданные внутри таких функций, по-прежнему не наследуют изоляцию, что соответствует принципам предсказуемости.
Практический смысл - баланс между безопасностью и удобством:
Это изменение - не просто синтаксический сахар. Оно решает фундаментальную проблему: делает поведение nonisolated функций последовательным и интуитивно понятным. Разработчики получают явный контроль:
🔵Хотите, чтобы функция работала в том же контексте и не требовала Sendable? Используйте nonisolated(nonsending).
🔵Нужно гарантированно переключиться для параллельной работы? Явно укажите
@concurrent.💡 Вывод:
Swift продолжает движение к модели, где безопасность от гонок данных достигается не через неявные, сложные правила, а через явные и понятные инструменты, дающие разработчику полный контроль над изоляцией. SE-0461 закрывает один из последних существенных пробелов в этой картине, делая асинхронное программирование в Swift более последовательным, предсказуемым и, как следствие - доступным для написания сложных, но корректных приложений.
➡️ Подписаться на канал
Мобильный трудоголик