


許多語言,例如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 . . .
}
}