


Banyak bahasa, seperti Kotlin, GO, JavaScript, Python, Rust, C#, C ++ dan lainnya, sudah memiliki dukungan coroutine yang memungkinkan implementasi pola async/menunggu. Fitur ini belum didukung dalam Swift, tetapi ini dapat ditingkatkan dengan kerangka kerja tanpa perlu mengubah bahasa.
Pemrograman asinkron biasanya dikaitkan dengan callback. Cukup nyaman sampai ada terlalu banyak dari mereka dan mereka mulai bersarang. Kemudian disebut piramida malapetaka atau bahkan neraka panggilan balik .
Masalah lain dari pemrograman asinkron adalah penanganan kesalahan , karena mekanisme penanganan kesalahan alami Swift tidak dapat digunakan.
Ada banyak kerangka kerja lain yang memudahkan untuk menggunakan kode asinkron, seperti Combine, RXSwift, Promeckit dan sebagainya. Mereka menggunakan pendekatan lain yang memiliki beberapa kelemahan:
Pola async/wait adalah alternatif yang memungkinkan fungsi asinkron dan tidak blocking disusun dengan cara yang mirip dengan fungsi sinkron biasa.
Ini sudah mapan dalam bahasa pemrograman lain dan merupakan evolusi dalam pemrograman asinkron. Implementasi pola ini dimungkinkan berkat Coroutine.
Mari kita lihat contoh dengan coroutine di dalamnya yang await() menangguhkannya dan melanjutkan ketika hasilnya tersedia tanpa memblokir utas.
//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
}Dokumentasi API
Coroutine adalah perhitungan yang dapat ditangguhkan dan dilanjutkan di lain waktu tanpa memblokir utas. Coroutine dibangun berdasarkan fungsi reguler dan dapat dieksekusi pada penjadwal apa pun dengan kemungkinan untuk beralih di antara mereka selama eksekusi.
Desain API Coroutines seminimalis mungkin. Ini terdiri dari protokol CoroutineScheduler yang menjelaskan cara menjadwalkan coroutine ( DispatchQueue sudah menyesuaikannya), dan struktur Coroutine dengan metode utilitas. API ini sudah cukup untuk melakukan hal -hal luar biasa.
Contoh berikut menunjukkan penggunaan await() di dalam coroutine untuk membungkus panggilan asinkron.
//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 . . .
} Begini cara kami dapat menyesuaikan NSManagedObjectContext ke CoroutineScheduler untuk meluncurkan Coroutine di atasnya.
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 ) }
}Masa depan adalah pemegang hanya baca untuk hasil yang akan diberikan nanti dan janji adalah penyedia hasil ini. Mereka mewakili penyelesaian akhirnya atau kegagalan operasi asinkron.
Pendekatan Futures and Promises itu sendiri telah menjadi standart industri. Ini adalah mekanisme yang nyaman untuk menyinkronkan kode asinkron. Tetapi bersama dengan coroutines, dibutuhkan penggunaan kode asinkron ke tingkat berikutnya dan telah menjadi bagian dari pola async/menunggu. Jika coroutine adalah kerangka, maka berjangka dan janji adalah ototnya.
Futures dan janji diwakili oleh kelas CoFuture yang sesuai dan subkelas CoPromise -nya.
//wraps some async func with CoFuture
func makeIntFuture ( ) -> CoFuture < Int > {
let promise = CoPromise < Int > ( )
someAsyncFunc { int in
promise . success ( int )
}
return promise
} Hal ini memungkinkan untuk memulai beberapa tugas secara paralel dan menyinkronkannya nanti dengan 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 ) "
} Sangat mudah untuk mengubah atau menyusun CoFuture menjadi yang baru.
let array : [ CoFuture < Int > ]
//create new CoFuture<Int> with sum of future results
let sum = CoFuture { try array . reduce ( 0 ) { try $0 + $1 . await ( ) } }Futures and Promises memberikan cara yang nyaman untuk mentransfer nilai tunggal antara coroutine. Saluran menyediakan cara untuk mentransfer aliran nilai. Secara konseptual, saluran mirip dengan antrian yang memungkinkan untuk menangguhkan coroutine saat menerima jika kosong, atau pada Kirim jika penuh.
Primitif non-blocking ini banyak digunakan dalam bahasa seperti GO dan Kotlin, dan itu adalah instrumen lain yang meningkatkan bekerja dengan coroutine.
Untuk membuat saluran, gunakan kelas 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 " )
} Semua coroutine yang diluncurkan, CoFuture S dan CoChannel S, biasanya tidak perlu dirujuk. Mereka dihapus setelah eksekusi mereka. Tetapi seringkali ada kebutuhan untuk menyelesaikannya lebih awal, ketika mereka tidak lagi dibutuhkan. Untuk ini, CoFuture dan CoChannel memiliki metode untuk membatalkan.
CoScope memudahkan mengelola siklus hidup benda -benda ini. Ini memungkinkan Anda untuk menyimpan referensi yang lemah kepada mereka dan membatalkan jika perlu atau di Deinit.
Anda dapat menambahkan coroutine, CoFuture S, CoChannel S dan CoCancellable lainnya ke CoScope untuk membatalkannya ketika mereka tidak lagi diperlukan atau di 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 . . .
}
}