Как устроен .task в SwiftUI

Модификатор .task { ... } в SwiftUI легко принять за сокращённую запись обычного Task { ... }. В обоих случаях разработчик передаёт асинхронный блок, поэтому кажется, что SwiftUI просто запускает этот блок при появлении вью.
Такое представление лишь частично описывает поведение модификатора. В документации Apple говорится, что .task добавляет асинхронную задачу, которую нужно выполнить до появления вью. При этом отдельно подчёркивается связь задачи с идентичностью вью: пока вью сохраняет свою identity, задача продолжает существовать, а при потере этой идентичности SwiftUI отменяет её.
Почему идентичность важнее самого появления
Формулировка о появлении вью может создать впечатление, что задача запускается при каждом визуальном появлении элемента на экране и сразу прекращается после исчезновения. Однако ключевым понятием здесь остаётся не только видимость, а именно идентичность вью в иерархии SwiftUI.
SwiftUI постоянно пересчитывает body, а структуры вью при этом создаются заново. Поэтому экземпляр вью нельзя рассматривать как контейнер, который надёжно хранит ссылку на запущенную задачу. Структура — это значение, и вместе с очередным экземпляром исчезли бы и данные, размещённые непосредственно внутри него.
Где тогда находится задача
Именно здесь возникает главный вопрос о внутреннем устройстве .task. Если задача не хранится в структуре вью, SwiftUI должен связывать её с самим жизненным циклом элемента и его идентичностью каким-то другим способом. Публичная документация описывает наблюдаемое поведение — запуск асинхронной работы и отмену при потере identity, — но не раскрывает, где именно размещается ссылка на задачу и какой внутренний механизм отвечает за это связывание.
Поэтому .task корректнее воспринимать не как обычный Task с точкой перед именем, а как декларативное описание асинхронной работы, привязанной к вью. При анализе конкретного сценария важно выяснять не только, появляется ли элемент на экране, но и сохраняет ли он ту же идентичность после пересчёта интерфейса.


