Всем привет! Сегодня разберемся с темой, которая вызывает много вопросов при переходе на
Swift Concurrency: как правильно использовать
[weak self] в
Task.
Если вы работали с
completion handlers, то у вас уже выработался рефлекс, почти в каждое замыкание добавлять
[weak self] и делать классический
guard let self. Кажется логичным перенести этот подход и в
Task:
Task { [weak self] in
guard let self else { return }
let data = await loadData()
...
}
Но вот в чем проблема: этот код не предотвращает утечку памяти так, как вы ожидаете.
Почему это происходит:
Task начинает выполнение практически мгновенно после создания. Вероятность того, что за микросекунды между созданием
Task и первой строчкой его кода
self будет освобожден крайне мала.
Когда вы делаете
guard let self, вы создаете сильную ссылку на объект. Теперь
Task будет удерживать
self до своего завершения, до окончания всех
await вызовов. Это эквивалентно тому, если бы вы в старом коде с
completion handlers написали просто
self.loadData() без всякого
[weak self].
Когда это действительно становится проблемой?
Представьте долгоиграющую задачу, например, загрузку данных постранично:
Task { [weak self] in
guard let self else { return } // Проблема здесь
var hasMorePages = true
while hasMorePages {
let page = await fetchNextPage()
hasMorePages = !page.isLastPage
}
}
Если пользователь уйдет с экрана,
self не сможет освободиться, потому что
Task продолжит свою работу и удерживает его до окончания цикла.
Правильное решение:
Вместо немедленного разворачивания
self, делайте это точечно, непосредственно перед использованием:
Task { [weak self] in
var hasMorePages = true
while hasMorePages {
// Проверяем self на каждой итерации
guard let self else {
hasMorePages = false
break
}
let page = await self.fetchNextPage()
hasMorePages = !page.isLastPage
}
}
Теперь сильная ссылка на
self создается только на время одной итерации цикла. Если
self освободится, цикл немедленно завершается.
Рекомендации:
- Не всегда нужно [weak self]: для коротких Task это часто избыточно.
- Избегайте guard let self в начале Task: это не дает преимуществ, но может продлить жизнь объекта.
- Разворачивайте self непосредственно перед использованием: минимизируйте время удержания сильной ссылки.
- Рассмотрите захват конкретных свойств: если вам нужен не весь self, а только некоторые его свойства, захватите их отдельно.
Вывод:
Главное понимать, что механизм работы
Task отличается от обычных замыканий, и слепое копирование старых паттернов может привести к неожиданным результатам.