


Muitos idiomas, como Kotlin, GO, JavaScript, Python, Rust, C#, C ++ e outros, já têm suporte de coroutinas que possibilitam a implementação do padrão assíncrono/aguardando. Esse recurso ainda não é suportado no Swift, mas isso pode ser melhorado por uma estrutura sem a necessidade de alterar o idioma.
A programação assíncrona geralmente está associada a retornos de chamada. É bastante conveniente até que haja muitos deles e eles começam a nidificar. Então é chamado de pirâmide de desgraça ou até um inferno de retorno de chamada .
Outro problema de programação assíncrona é o manuseio de erros , porque o mecanismo de manuseio de erro natural de Swift não pode ser usado.
Existem muitas outras estruturas que facilitam o uso de código assíncrono, como Combine, RxSwift, PromiseKit e assim por diante. Eles usam outras abordagens que têm algumas desvantagens:
O padrão assíncrono/aguardado é uma alternativa que permite que uma função assíncrona e não bloqueadora seja estruturada de maneira semelhante a uma função síncrona comum.
Já está bem estabelecido em outras linguagens de programação e é uma evolução na programação assíncrona. A implementação desse padrão é possível graças às coroutinas.
Vamos dar uma olhada no exemplo com a Coroutine dentro da qual await() suspende -a e retoma quando o resultado está disponível sem bloquear o thread.
//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
}Documentação da API
Uma coroutina é um cálculo que pode ser suspenso e retomado posteriormente sem bloquear um thread. As coroutinas se baseiam em funções regulares e podem ser executadas em qualquer agendador com a possibilidade de alternar entre elas durante a execução.
O design da API Coroutines é o mais minimalista possível. Consiste no protocolo CoroutineScheduler que descreve como agendar coroutinas ( DispatchQueue já o está conforme) e a estrutura Coroutine com métodos de utilidade. Esta API é suficiente para fazer coisas incríveis.
O exemplo a seguir mostra o uso do await() dentro de uma coroutina para envolver chamadas assíncronas.
//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 . . .
} Veja como podemos conformar NSManagedObjectContext com CoroutineScheduler para lançar coroutinas nele.
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 ) }
}Um futuro é um titular somente leitura para um resultado que será fornecido posteriormente e a promessa é o fornecedor desse resultado. Eles representam a eventual conclusão ou falha de uma operação assíncrona.
A abordagem de futuros e promessas em si tornou -se uma indústria independente. É um mecanismo conveniente para sincronizar o código assíncrono. Mas, juntamente com as Coroutines, ele leva o uso de código assíncrono para o próximo nível e se tornou parte do padrão de assíncrono/aguardar. Se as coroutinas são um esqueleto, os futuros e as promessas são seus músculos.
Futuros e promessas são representados pela classe CoFuture correspondente e sua subclasse CoPromise .
//wraps some async func with CoFuture
func makeIntFuture ( ) -> CoFuture < Int > {
let promise = CoPromise < Int > ( )
someAsyncFunc { int in
promise . success ( int )
}
return promise
} Ele permite iniciar várias tarefas em paralelo e sincronizá -las posteriormente com 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 ) "
} É muito fácil transformar ou compor CoFuture em um novo.
let array : [ CoFuture < Int > ]
//create new CoFuture<Int> with sum of future results
let sum = CoFuture { try array . reduce ( 0 ) { try $0 + $1 . await ( ) } }Os futuros e as promessas fornecem uma maneira conveniente de transferir um único valor entre as coroutinas. Os canais fornecem uma maneira de transferir um fluxo de valores. Conceitualmente, um canal é semelhante a uma fila que permite suspender uma coroutina no recebimento se estiver vazia ou se enviar se estiver cheia.
Esse primitivo não bloqueador é amplamente utilizado em idiomas como Go e Kotlin, e é outro instrumento que melhora o trabalho com coroutinas.
Para criar canais, use a classe 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 " )
} Todos lançaram coroutinas, CoFuture e CoChannel , geralmente não precisam ser referenciados. Eles são desinitados após sua execução. Mas muitas vezes é necessário concluí -los mais cedo, quando não são mais necessários. Para isso, CoFuture e CoChannel têm métodos para cancelar.
CoScope facilita o gerenciamento do ciclo de vida desses objetos. Ele permite que você mantenha referências fracas a elas e cancele, se necessário, ou em Deinit.
Você pode adicionar coroutinas, CoFuture , CoChannel se e outras CoCancellable ao CoScope para cancelá -las quando não forem mais necessárias ou em 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 . . .
}
}