Примерный пример кода, который мне нужно получить с помощью GCD:
func performTask(completion: (() -> ())?) {
doSomeAsyncTask { [weak self] isSuccess in
if isSuccess {
self?.handleResult(completion: completion)
} else {
completion?()
self?.handleResult(completion: nil)
}
}
}
func handleResult(completion: (() -> ())?) {
…
}
func doSomeAsyncTask(completion: ((Bool) -> ())?) {
//example of async task which can be successful or not
DispatchQueue.main.async {
completion?(Bool.random())
}
}
Я хочу переписать его с помощью async/await
, но не знаю, как реализовать performTask
. Другие методы:
func handleResult() async {
…
}
func doSomeAsyncTask() async -> Bool {
await withCheckedContinuation { checkedContinuation in
Task {
checkedContinuation.resume(returning: Bool.random())
}
}
}
Не могли бы вы объяснить, как реализовать performTask
? Я не могу понять, как быть с ситуацией, когда иногда метод должен вызывать завершение, а иногда нет.
Несвязанное наблюдение: любопытно, что doSomeAsyncTask
выполняет некоторую работу, но не возвращает никакого результата (кроме логического значения) и что handleResult
не передает результат для обработки. Я предполагаю, что фактический результат хранится в каком-то свойстве. Обычно предпочтительнее, чтобы doSomeAsyncTask
фактически возвращал результаты напрямую (и либо выдавал ошибку, либо возвращал nil
в случае сбоя), а затем по мере необходимости предоставлял это значение в качестве параметра handleResult
. Здесь недостаточно подробностей (и это выходит за рамки вопроса), это просто к вашему сведению.
@Rob в моем конкретном случае doSomeAsyncTask
загружает объявление, handleResult
перезагружает объявление с некоторыми проверками и интервалами времени, completion
обычно показывает это объявление. Эта внутренняя логика означает «загрузить объявление. Если оно загружено, покажите его и продолжите поток после закрытия объявления. Если есть ошибка, продолжайте поток, но попробуйте предварительно загрузить объявление (возможно, в следующий раз оно будет готово).
Если это проблема бизнеса, я был бы склонен использовать совсем другой подход, а именно асинхронную последовательность, например, AsyncTimerSequence, которая через некоторый интервал переходит к следующему объявлению.
В вашей версии обработчика завершения, если doSomeAsyncTask
возвращает false
, оно немедленно вызывает замыкание, а затем вызывает handleResult
с nil
для обработчика завершения. Это очень любопытная закономерность. Единственное, что я могу догадаться, это то, что намерение состоит в том, чтобы немедленно вызвать обработчик завершения, если doSomeAsyncTask
не удалось, но все равно вызвать handleResult
независимо, но в случае успеха не возвращаться, пока handleResult
не будет выполнено.
Если это ваше намерение, параллельная обработка Swift может быть такой:
@discardableResult
func performTask() async -> Bool {
let isSuccess = await doSomeAsyncTask()
if isSuccess {
await handleResult()
} else {
Task { await handleResult() }
}
return isSuccess
}
Обратите внимание: я возвращаю успех или неудачу doSomeAsyncTask
, потому что, как правило, вы всегда хотите, чтобы вызывающая сторона имела возможность узнать, удалось ли это или нет (даже если вас это сейчас не волнует). Итак, я поставил @discardableResult
на случай, если вызывающему абоненту в данный момент все равно, удалось ли это или нет.
Другой шаблон — выдать ошибку в случае неудачи (и вызывающая сторона может try?
, если ее не волнует успех или неудача на данном этапе).
enum ProcessError: Error {
case failed
}
func performTask() async throws {
if await doSomeAsyncTask() {
await handleResult()
} else {
Task { await handleResult() }
throw ProcessError.failed
}
}
Показав дословный перевод предоставленного вами кода, я мог бы предложить более простой шаблон:
@discardableResult
func performTask() async -> Bool {
let isSuccess = await doSomeAsyncTask()
await handleResult()
return isSuccess
}
В первых двух альтернативах, описанных выше, я использую неструктурированный параллелизм для обработки сценария, в котором doSomeAsyncTask
возвращает false
, и разрешаю функции немедленно возвращать результат, а затем выполнять handleResult
асинхронно позже. Это (как и воспроизведение обработчика завершения) вызывает вопрос, как обрабатывать отмену. И вопрос в том, достаточно ли медленный handleResult
, чтобы оправдать эту закономерность, или это был случай преждевременной оптимизации. Кажется чрезвычайно странным, что можно ожидать handleResult
на пути успеха, а не на пути неудач. Третий и последний пример упрощает эту логику. Мы недостаточно знаем о причинах исходной версии обработчика завершения, чтобы ответить на этот вопрос на данный момент.
Посмотрите «Meet async/await». Apple проходит весь процесс преобразования. Кроме того, эта плавающая задача не является обязательной и обычно является причиной утечки.