


العديد من اللغات ، مثل Kotlin و Go و JavaScript و Python و Rust و C#و C ++ وغيرها ، لديها بالفعل دعم Coroutines مما يجعل تنفيذ نمط Async/انتظار ممكن. لم يتم دعم هذه الميزة بعد في Swift ، ولكن يمكن تحسين ذلك من خلال إطار دون الحاجة إلى تغيير اللغة.
عادة ما ترتبط البرمجة غير المتزامنة بالتراجع. إنه مناسب تمامًا حتى يكون هناك الكثير منهم ويبدأون في التعشيش. ثم يطلق عليه هرم من الموت أو حتى جحيم رد الاتصال .
هناك مشكلة أخرى من البرمجة غير المتزامنة وهي معالجة الأخطاء ، لأنه لا يمكن استخدام آلية معالجة الأخطاء الطبيعية لـ Swift.
هناك العديد من الأطر الأخرى التي تجعل من السهل استخدام التعليمات البرمجية غير المتزامنة ، مثل الجمع ، Rxswift ، PromiseKit وما إلى ذلك. يستخدمون مناهج أخرى لها بعض العيوب:
نمط Async/Await هو بديل يسمح بتنظيم وظيفة غير متزامنة غير محظورة بطريقة تشبه وظيفة متزامنة عادية.
إنه بالفعل راسخ في لغات البرمجة الأخرى وهو تطور في البرمجة غير المتزامنة. تنفيذ هذا النمط ممكن بفضل 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 على وظائف منتظمة ويمكن تنفيذها على أي جدولة مع إمكانية التبديل بينها أثناء التنفيذ.
تصميم API Coroutines هو الحد الأدنى قدر الإمكان. وهو يتألف من بروتوكول 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 ، فإن الأمر يأخذ استخدام الكود غير المتزامن إلى المستوى التالي وأصبح جزءًا من نمط Async/الانتظار. إذا كانت 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 ( ) } }توفر العقود الآجلة والوعود طريقة مريحة لنقل قيمة واحدة بين coroutines. توفر القنوات وسيلة لنقل دفق من القيم. من الناحية المفاهيمية ، تشبه القناة قائمة انتظار تسمح بتعليق coroutine على الاستقبال إذا كانت فارغة ، أو عند إرسالها إذا كانت ممتلئة.
يستخدم هذه البدائية غير المحظورة على نطاق واسع في مثل هذه اللغات مثل 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 . . .
}
}