


Многие языки, такие как Kotlin, Go, JavaScript, Python, Rust, C#, C ++ и другие, уже имеют поддержку Coroutines, которая делает возможной реализацию шаблона Async/Await. Эта функция еще не поддерживается в Swift, но это может быть улучшено в рамках без необходимости изменить язык.
Асинхронное программирование обычно связано с обратными вызовами. Это довольно удобно, пока их не станет слишком много, и они начнут гнездясь. Тогда это называется пирамидой гибели или даже в аду .
Другая проблема асинхронного программирования - обработка ошибок , потому что естественный механизм обработки ошибок Swift не может быть использован.
Есть много других структур, которые облегчают использование асинхронного кода, такие как Combine, RXSWIFT, Orderkit и так далее. Они используют другие подходы, которые имеют некоторые недостатки:
Асинхронный/ожидающий шаблон-это альтернатива, которая позволяет асинхронной неблокирующей функции структурироваться так, как и на обычную синхронную функцию.
Он уже хорошо известен в других языках программирования и является эволюцией в асинхронном программировании. Реализация этого шаблона возможна благодаря Coroutines.
Давайте посмотрим на пример с Coroutine, внутри которого await() подвекает его и возобновляется, когда результат доступен без блокирования потока.
//executes coroutine on the main thread
DispatchQueue . main . startCoroutine {
//extension that returns CoFuture<(data: Data, response: URLResponse)>
let dataFuture = URLSession . shared . dataTaskFuture ( for : imageURL )
//await CoFuture result that suspends coroutine and doesn't block the thread
let data : Data = try dataFuture . await ( ) . data
//create UIImage from the data
guard let image = UIImage ( data : data ) else { return }
//execute heavy task on global queue and await the result without blocking the thread
let thumbnail : UIImage = try DispatchQueue . global ( ) . await { image . makeThumbnail ( ) }
//set image in UIImageView on the main thread
self . imageView . image = thumbnail
}Документация API
Коратика - это вычисление, которое можно приостановить и возобновить позднее, не блокируя поток. Coroutines опираются на регулярные функции и могут быть выполнены на любом планировщике с возможностью переключения между ними во время выполнения.
Дизайн API Coroutines максимально минималистичен. Он состоит из протокола CoroutineScheduler , который описывает, как планировать Coroutines ( DispatchQueue уже соответствует его), и структуры Coroutine с помощью полезности. Этого API достаточно, чтобы делать удивительные вещи.
В следующем примере показано использование await() внутри коратики, чтобы обернуть асинхронные вызовы.
//execute coroutine on the main thread
DispatchQueue . main . startCoroutine {
//await URLSessionDataTask response without blocking the thread
let ( data , response , error ) = try Coroutine . await { callback in
URLSession . shared . dataTask ( with : url , completionHandler : callback ) . resume ( )
}
. . . use response on the main thread . . .
} Вот как мы можем соответствовать NSManagedObjectContext с CoroutineScheduler для запуска CORUTINES.
extension NSManagedObjectContext : CoroutineScheduler {
func scheduleTask ( _ task : @escaping ( ) -> Void ) {
perform ( task )
}
}
//execute coroutine on the main thread
DispatchQueue . main . startCoroutine {
let context : NSManagedObjectContext //context with privateQueueConcurrencyType
let request : NSFetchRequest < NSDictionary > //some complex request
//execute request on the context without blocking the main thread
let result : [ NSDictionary ] = try context . await { try context . fetch ( request ) }
}Будущее является владельцем только для чтения для результата, который будет предоставлен позже, и обещание является поставщиком этого результата. Они представляют возможное завершение или неудачу асинхронной операции.
Сам подход Futures and обещания стал отраслью. Это удобный механизм синхронизации асинхронного кода. Но вместе с Coroutines он выводит использование асинхронного кода на следующий уровень и стало частью шаблона Async/Await. Если Coroutines являются скелетом, то будущее и обещания - это мышцы.
Фьючерсы и обещания представлены соответствующим классом CoFuture и его подклассом CoPromise .
//wraps some async func with CoFuture
func makeIntFuture ( ) -> CoFuture < Int > {
let promise = CoPromise < Int > ( )
someAsyncFunc { int in
promise . success ( int )
}
return promise
} Это позволяет запускать несколько задач параллельно и синхронизировать их позже с await() .
//create CoFuture<Int> that takes 2 sec. from the example above
let future1 : CoFuture < Int > = makeIntFuture ( )
//execute coroutine on the global queue and returns CoFuture<Int> with future result
let future2 : CoFuture < Int > = DispatchQueue . global ( ) . coroutineFuture {
try Coroutine . delay ( . seconds ( 3 ) ) //some work that takes 3 sec.
return 6
}
//execute coroutine on the main thread
DispatchQueue . main . startCoroutine {
let sum : Int = try future1 . await ( ) + future2 . await ( ) //will await for 3 sec.
self . label . text = " Sum is ( sum ) "
} Это очень легко трансформировать или составить CoFuture S в новый.
let array : [ CoFuture < Int > ]
//create new CoFuture<Int> with sum of future results
let sum = CoFuture { try array . reduce ( 0 ) { try $0 + $1 . await ( ) } }Фьючерсы и обещания обеспечивают удобный способ перенести единого значения между Coroutines. Каналы обеспечивают способ перенести поток значений. Концептуально, канал похож на очередь, которая позволяет приостановить кораку на получении, если он пуст, или при отправке, если он заполнен.
Этот неблокирующий примитив широко используется на таких языках, как Go и Kotlin, и это еще один инструмент, который улучшает работу со Corubines.
Чтобы создать каналы, используйте класс CoChannel .
//create a channel with a buffer which can store only one element
let channel = CoChannel < Int > ( capacity : 1 )
DispatchQueue . global ( ) . startCoroutine {
for i in 0 ..< 100 {
//imitate some work
try Coroutine . delay ( . seconds ( 1 ) )
//sends a value to the channel and suspends coroutine if its buffer is full
try channel . awaitSend ( i )
}
//close channel when all values are sent
channel . close ( )
}
DispatchQueue . global ( ) . startCoroutine {
//receives values until closed and suspends a coroutine if it's empty
for i in channel . makeIterator ( ) {
print ( " Receive " , i )
}
print ( " Done " )
} Все запускаемые Coroutines, CoFuture S и CoChannel S, обычно не нужно ссылаться. Они определяются после их исполнения. Но часто есть необходимость завершить их раньше, когда они больше не нужны. Для этого CoFuture и CoChannel есть методы отмены.
CoScope облегчает управление жизненным циклом этих объектов. Это позволяет вам сохранять слабые ссылки на них и отменить, если это необходимо, или на Deinit.
Вы можете добавить Coroutines, CoFuture S, CoChannel S и другие CoCancellable в CoScope чтобы отменить их, когда они больше не нужны или на Deinit.
class ViewController : UIViewController {
let scope = CoScope ( ) //will cancel all objects on `cancel()` or deinit
func performSomeWork ( ) {
//create new `CoChannel` and add to `CoScope`
let channel = makeSomeChannel ( ) . added ( to : scope )
//execute coroutine and add to `CoScope`
DispatchQueue . main . startCoroutine ( in : scope ) { [ weak self ] in
for item in channel . makeIterator ( ) {
try self ? . performSomeWork ( with : item )
}
}
}
func performSomeWork ( with item : Item ) throws {
//create new `CoFuture` and add to `CoScope`
let future = makeSomeFuture ( item ) . added ( to : scope )
let result = try future . await ( )
. . . do some work using result . . .
}
}