Пример кода:
func someAsyncFunc() async {
loadingTask?.cancel()
return await withCheckedContinuation { [weak self] checkedContinuation in
guard let self else {
return
}
loadingTask = Task { [weak self] in
guard let self else {
return
}
//some code with self
checkedContinuation.resume()
}
}
}
Я понимаю, что один из вызовов [weak self] является лишним — в этом примере просто показаны 2 возможных фрагмента кода, где его можно разместить.
Согласно одной статье документации withCheckedContinuation всегда следует вызывать checkedContinuation.resume(), согласно другой статье документации следует использовать [weak self], чтобы избежать сильных циклов ссылок. Но как использовать их оба одновременно?
Если я оставлю код КАК ЕСТЬ, то checkedContinuation.resume() может не вызваться и это приведет к утечкам памяти. Если я не использую [weak self], это приводит к циклам сохранения, что также приводит к утечкам памяти. Я также не могу просто заменить withCheckedContinuation на withCheckedThrowingContinuation, потому что это увеличивает сложность кода, а также означает, что мне следует забыть о withCheckedContinuation (потому что любой подобный метод может содержать [weak self]).
1) как использовать его напрямую? Я пробовал различные комбинации кода, но всегда получал ошибки компиляции или, по крайней мере, предупреждения; 2) вместо Task может быть другая асинхронная функция (например, функция с блоком завершения), но проблема с [weak self] остаётся.





with*Continuation предназначен для упаковки устаревшего кода, который использует блоки завершения и т. д., в асинхронном контексте. Вы предпочитаете вообще не использовать его и заставить всю функцию работать в вызывающем асинхронном контексте (который затем также должен поддерживать каскадную отмену вместо необходимости управлять задачей отдельно).
Вы также рискуете не возобновить продолжение, если someAsyncFunc вызывается повторно, что имеет больший вес, чем простое использование асинхронных функций напрямую.
Если после всего этого у вас все еще есть веская причина продолжать использовать продолжения, вы можете рассматривать освобождение себя как эквивалент отмены и бросить CancellationError, или вы можете возобновить продолжение со значением Void (()), поскольку это тип возвращаемого значения всей функции.
Несколько наблюдений:
Обычно мы используем [weak self], чтобы избежать сильных циклов ссылок. Здесь нет постоянного цикла сильных ссылок, поэтому [weak self] не нужен.
Иногда мы использовали шаблон [weak self] в тех ситуациях, когда не было постоянного цикла сильных ссылок, а была лишь временная ссылка на self во время выполнения асинхронной задачи.
Но даже в этом случае использовать [weak self] не рекомендуется. Сегодня вместо того, чтобы беспокоиться о продолжительности жизни self, мы переключаем внимание на продолжительность жизни асинхронной задачи. В частности, мы обязательно отменяем асинхронную работу, как только ее результаты больше не нужны. Это делает шаблон [weak self] спорным.
Во времена НОД отмена была неуклюжей. Но параллелизм Swift предлагает первоклассную поддержку отмены, поэтому мы должны принять это, отменяя задачу при закрытии представления (например, в UIKit/AppKit в viewDidDisappear, в SwiftUI в .onDisappear).
Учитывая все вышесказанное, предположим, что вы действительно хотели сделать что-то вроде того, что задумано в вашем вопросе. (В этом нет необходимости, и это плохой дизайн, но давайте обсудим это просто как мысленное упражнение.)
Рассмотрим фрагмент кода:
func someAsyncFunc() async {
loadingTask?.cancel()
return await withCheckedContinuation { [weak self] checkedContinuation in
guard let self else {
return
}
loadingTask = Task { [weak self] in
guard let self else {
return
}
//some code with self
checkedContinuation.resume()
}
}
}
С этим шаблоном guard let self else { return } есть две проблемы:
Во-первых, задачи обычно запускаются немедленно, поэтому вы, как правило, передаете операторы guard до того, как self, возможно, будет деинициализировано, поэтому эти операторы guard ничего не выполнят.
Во-вторых, как вы заметили, в этих утверждениях guard вы возвращаетесь без вызова resume, что недействительно. В withCheckedContinuation необходимо resume один и только один раз. Но ты возвращаешься, так и не позвонив resume. Если это произойдет (что крайне маловероятно, учитывая мой первый пункт), проверенное продолжение сообщит об ошибке. Вы должны resume продолжить перед собой return в этих guard утверждениях (при условии, что вы guard вообще есть).
Обратите внимание: при использовании шаблонов отмены вы обычно используете withTaskCancellationHandler, try Task.checkCancellation(), тест Task.isCancelled() и т. д., чтобы отреагировать на отмену. Но нам нужно будет увидеть «какой-то код с самим собой», чтобы дать дальнейшие советы (и, вероятно, лучше всего обратиться к нему в отдельном вопросе). Если вы не внедрите поддержку отмены, ваш task.cancel() ничего не даст.
Но, опять же, не вставляйте шаблон [weak self] вслепую, опасаясь потенциального риска сильного ссылочного цикла, поскольку здесь нет сильного ссылочного цикла, и это своего рода антишаблон в параллелизме Swift. Вместо этого следует принять шаблоны отмены.
Почему у тебя
TaskвwithCheckedContinuation? А как насчетawaitнепосредственноTask?