


Kotlin、Go、JavaScript、Python、Rust、C#、C ++などの多くの言語には、すでにCoroutinesのサポートがあり、Async/待望のパターン実装を可能にしています。この機能はまだSwiftではサポートされていませんが、これは言語を変更する必要なく、フレームワークによって改善できます。
非同期プログラミングは通常、コールバックに関連付けられています。それらが多すぎて、それらが営巣を開始するまで、それは非常に便利です。それから、それは運命のピラミッドと呼ばれます。
非同期プログラミングのもう1つの問題は、Swiftの自然なエラー処理メカニズムを使用できないため、エラー処理です。
Combine、RxSwift、PromiseKitなど、非同期コードを簡単に使用できるようにする他の多くのフレームワークがあります。彼らはいくつかの欠点を持つ他のアプローチを使用します:
Async/awaitパターンは、非同期の非ブロッキング関数を通常の同期関数と同様の方法で構成できるようにする代替案です。
他のプログラミング言語ではすでに確立されており、非同期プログラミングの進化です。このパターンの実装は、Coroutinesのおかげで可能です。
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は通常の機能に基づいて構築され、実行中にそれらの間に切り替える可能性があるスケジューラで実行できます。
Coroutines API設計は、可能な限り最小限です。これは、Coroutinesのスケジュール( DispatchQueue既に適合している)を説明するCoroutineSchedulerプロトコルと、ユーティリティ方法を使用した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 . . .
} Coroutinesを起動するために、 NSManagedObjectContext CoroutineSchedulerに適合させる方法は次のとおりです。
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 ) }
}Futureは、後で提供される結果の読み取り専用ホルダーであり、この結果のプロバイダーです。それらは、非同期操作の最終的な完了または障害を表します。
先物と約束のアプローチ自体は、業界のスタンドアートになりました。非同期コードを同期するための便利なメカニズムです。しかし、Coroutinesとともに、非同期コードの使用法を次のレベルに引き上げ、Async/待望のパターンの一部になりました。コルーチンが骨格である場合、未来と約束はその筋肉です。
先物と約束は、対応する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 " )
}すべてが発売されたコルーチン、 CoFuture S、 CoChannel Sは、通常、参照する必要はありません。彼らは処刑後に決定されます。しかし、多くの場合、それらがもはや必要でないときに、それらを以前に完了する必要があります。このため、 CoFutureとCoChannelはキャンセルの方法があります。
CoScopeにより、これらのオブジェクトのライフサイクルを簡単に管理できます。必要に応じて、または必要に応じてキャンセルすることができます。
CoScopeにコスコープにコスコープをキャンセルできるコルーチン、 CoFuture 、 CoChannel S、その他のCoCancellableが追加して、不要になったときにキャンセルできます。
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 . . .
}
}