


许多语言,例如Kotlin,GO,JavaScript,Python,Rust,C#,C ++等,已经具有使异步/等待模式实现成为可能的Coroutines支持。 Swift尚未支持此功能,但可以通过框架不需要更改语言来改善这一功能。
异步编程通常与回调有关。这很方便,直到它们太多了,并且它们开始筑巢。然后被称为厄运的金字塔,甚至是回调地狱。
异步编程的另一个问题是错误处理,因为无法使用Swift的自然错误处理机制。
还有许多其他框架使其易于使用异步代码,例如Combine,RxSwift,PromiseKit等。他们使用其他有一些缺点的方法:
异步/等待模式是一种替代方案,它允许以类似于普通同步函数的方式构造异步,非阻滞函数。
它已经在其他编程语言中已经建立了良好,并且是异步编程的发展。得益于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文档
Coroutine是一个计算,可以在以后的时间暂停并恢复而不会阻塞线程。 Coroutines以常规功能为基础,并且可以在任何调度程序上执行,并且可以在执行过程中切换。
Coroutines API设计尽可能简约。它由CoroutineScheduler协议组成,该协议描述了如何安排Coroutines( DispatchQueue已经符合它)以及与实用方法的Coroutine结构。这个API足以做惊人的事情。
下面的示例显示了await()在Coroutine内的用法,以包装异步调用。
//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构成的方式,以在其上启动Coroutines。
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 ) }
}未来是一个只读的持有人,可以在以后提供的结果,并且承诺是该结果的提供者。它们代表了异步操作的最终完成或失败。
未来和承诺方法本身已成为行业的立场。这是同步异步代码的方便机制。但是,它与Coroutines一起,将异步代码的用法提升到一个新的层面,并已成为异步/等待模式的一部分。如果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 ( ) } }期货和诺言提供了一种方便的方式,可以在共同点之间传输单个价值。通道提供了传输值流的方法。从概念上讲,通道类似于一个队列,该队列允许如果是空的,则可以将其悬挂在接收的情况下,或者如果已满,则在发送时。
这种非阻滞原始性被广泛用于GO和Kotlin等语言中,它是另一种改善与Coroutines合作的工具。
要创建频道,请使用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 . . .
}
}