[Перевод] Continuations для взаимодействия асинхронных задач с синхронным кодом

88dbd92f6f257bb1d0c403e97ea53077.jpeg

В Swift были представлены новые функции, которые помогают нам адаптировать старые API в стиле completionhandler к современному асинхронному коду.

Например, эта функция возвращает свои значения асинхронно, используя обработчик завершения:

import Foundation

func fetchLatestNews(completion: @escaping ([String]) -> Void) {
    DispatchQueue.main.async {
        completion(["Swift 5.5 release", "Apple acquires Apollo"])
    }
}

Если бы мы хотели использовать ее с помощью async/await, мы могли бы переписать функцию, но есть различные причины, по которым это может оказаться невозможным — например, она может быть взята из внешней библиотеки.

Continuations позволяют нам создать обертку над обработчиком завершения (completion handler) и асинхронными функциями, чтобы мы могли обернуть старый код в более современный API. Например, функция withCheckedContinuation () создает новое continuation, которое может запускать любой код, который мы хотим, а затем вызывает resume (returning:), чтобы отправить значение обратно, даже если это часть обработчика завершения (completion handler).

Итак, мы можем создать вторую асинхронную функцию fetchLatestNews (), обернутую вокруг старой функции с completionhandler:

func fetchLatestNews() async -> [String] {
    await withCheckedContinuation { continuation in
        fetchLatestNews { items in
            continuation.resume(returning: items)
        }
    }
}

Теперь мы можем получить нашу исходную функциональность в асинхронной функции, например, следующим образом:

func printNews() async {
    let items = await fetchLatestNews()

    for item in items {
        print(item)
    }
}

Термин «checked» continuation означает, что Swift выполняет проверки во время выполнения от нашего имени: вызываем ли мы resume () один и только один раз? Это важно, потому что, если мы никогда не вызовем resume (), произойдет утечка ресурсов, но если мы вызовем его дважды, то, скорее всего, столкнемся с проблемами.

Важно: Мы должны вызывать resume () ровно один раз.

Поскольку проверка наших continuations сопряжена с затратами и производительностью во время выполнения, Swift также предоставляет функцию withUnsafeContinuation (), которая работает точно так же, за исключением того, что не выполняет проверки во время выполнения. Это означает, что Swift не предупредит нас, если мы забудем вызвать resume (), и если мы вызовем его дважды, то поведение будет не определено.

Поскольку эти две функции вызываются одинаковым образом, мы можем легко переключаться между ними. Кажется наиболее вероятным, что люди будут использовать withCheckedContinuation () при написании своих функций, поэтому Swift будет выдавать предупреждения и даже вызывать сбои, если continuations используются неправильно, но некоторые могут затем переключиться на withUnsafeContinuation (), если для них критичны затраты и производительность во время выполнения checked continuations.

© Habrahabr.ru