
توفر هذه المكتبة دعم Coroutine للهدف C و Swift. أضفنا طريقة انتظار 、 نموذج مولد وممثل مثل C#、 JavaScript و Kotlin. للراحة ، أضفنا فئات Coroutine لبعض Foundation و Uikit API في إطار عمل Cokit مثل NsfileManager و JSON و NSData و Uiimage وما إلى ذلك. نضيف أيضًا دعم Tuple في Coobjc.
coobjc 中文文档
يعد رد الاتصال غير المتزامن غير المتزامن المستند إلى كتلة الطريقة الأكثر استخدامًا على نطاق واسع للبرمجة غير المتزامنة لنظام التشغيل iOS. مكتبة GCD التي توفرها نظام iOS تجعل التطوير غير المتزامن بسيطًا ومريحًا للغاية ، ولكن هناك العديد من العيوب القائمة على طريقة البرمجة هذه :
ادخل في جحيم رد الاتصال
يتكون تسلسل العمليات البسيطة بشكل غير طبيعي في الكتل المتداخلة. هذا "Callback Hell" يجعل من الصعب تتبع الكود الذي يتم تشغيله ، ويؤدي كومة الإغلاق إلى العديد من تأثيرات الدرجة الثانية.
يصبح التعامل مع الأخطاء أمرًا صعبًا وامسدًا للغاية
التنفيذ الشرطي صعب ومعرض للخطأ
ننسى استدعاء كتلة الانتهاء
لأن معالجات الانتهاء محرجة ، يتم تعريف الكثير من واجهات برمجة التطبيقات بشكل متزامن
من الصعب تحديد هذا ، لكن المؤلفين يعتقدون أن حرج تحديد واستخدام واجهات برمجة التطبيقات غير المتزامنة (باستخدام معالجات الانتهاء) قد أدى إلى تعريف العديد من واجهات برمجة التطبيقات بسلوك متزامن على ما يبدو ، حتى عندما يمكنهم حظر. هذا يمكن أن يؤدي إلى مشاكل في الأداء ومشاكل الاستجابة في تطبيقات واجهة المستخدم - مثل المؤشر الغزل. يمكن أن يؤدي أيضًا إلى تعريف واجهات برمجة التطبيقات التي لا يمكن استخدامها عندما يكون عدم التزامن أمرًا ضروريًا لتحقيق النطاق ، على سبيل المثال على الخادم.
حوادث متعددة الخيوط يصعب تحديدها
الأقفال وإساءة استخدام الإشارة الناتجة عن الحظر
واجهت هذه المشكلة في العديد من الأنظمة والعديد من اللغات ، وتجريد Coroutines هو وسيلة قياسية لمعالجتها. دون أن تخوض الكثير في النظرية ، فإن coroutines هي امتداد للوظائف الأساسية التي تسمح للدالة بإرجاع القيمة أو تعليقها. يمكن استخدامها لتنفيذ المولدات والنماذج غير المتزامنة والقدرات الأخرى - هناك مجموعة كبيرة من العمل على النظرية وتنفيذها وتحسينها.
Kotlin هي لغة برمجة ثابتة تدعمها JetBrains تدعم التطبيقات الحديثة متعددة المنصات. لقد كان الجو حارًا جدًا في مجتمع المطورين على مدار العامين الماضيين. في لغة kotlin ، أصبحت Async/تنتظر استنادًا إلى coroutine ، المولد/العائد وغيرها من التقنيات غير المتزامنة المعيارية ، مقدمة ذات صلة بـ Kotlin Coroutine ، يمكنك الرجوع إلى : https: //www.kotlincn.net/docs/reference/coroutines.html
Coroutines هي مكونات برنامج الكمبيوتر التي تعمم الروتين الفرعي للاعبين المتعدد غير المسبكي ، من خلال السماح بتعليق التنفيذ وإعادة تأمينه. Coroutines مناسبة تمامًا لتنفيذ مكونات البرنامج المألوفة مثل المهام التعاونية والاستثناءات وحلقات الأحداث والتكرار والقوائم اللانهائية والأنابيب
تم اقتراح مفهوم Coroutine في الستينيات. يستخدم على نطاق واسع في الخادم. إنه مناسب للغاية للاستخدام في سيناريوهات التزامن العالية. يمكن أن يقلل بشكل كبير من عدد الخيوط في جهاز واحد وتحسين إمكانات الاتصال ومعالجة الجهاز الواحد. في غضون ذلك ، لا يدعم iOS حاليًا استخدام coroutines (لهذا السبب نريد دعمه.)
COOBJC هو إطار عمل لتطوير Coroutine يمكن استخدامه على iOS من قبل فريق ALIBABA TAOBAO-Mobile Architecture. حاليًا يدعم استخدام Objective-C و Swift. نحن نستخدم لغة التجميع ولغة C للتنمية ، وتوفر الطبقة العليا الواجهة بين Objective-C و Swift. حاليًا ، إنه مفتوح المصدر هنا تحت رخصة Apache Open Source.
قم بإنشاء coroutine باستخدام طريقة CO_LAUNCH
co_launch (^{
...
});تم جدولة coroutine التي تم إنشاؤها بواسطة Co_Launch افتراضيًا في مؤشر الترابط الحالي.
في coroutine ، نستخدم طريقة الانتظار لانتظار الطريقة غير المتزامنة للتنفيذ ، والحصول على نتيجة التنفيذ غير المتزامنة
- ( void )viewDidLoad {
...
co_launch (^{
// async downloadDataFromUrl
NSData *data = await ( downloadDataFromUrl (url));
// async transform data to image
UIImage *image = await ( imageFromData (data));
// set image to imageView
self. imageView . image = image;
});
}يحول الرمز أعلاه الكود الذي يحتاج في الأصل إلى Dispatch_async مرتين إلى تنفيذ متسلسل ، والرمز أكثر إيجازًا.
في coroutine ، جميع أساليبنا تعيد القيمة مباشرة ، ولا يتم إرجاع أي خطأ. يتم الحصول على خطأنا في عملية التنفيذ بواسطة Co_geterror (). على سبيل المثال ، لدينا الواجهة التالية للحصول على بيانات من الشبكة. عندما يرفض الوعد: خطأ
- (COPromise*)co_GET:( NSString *)url parameters:( NSDictionary *)parameters{
COPromise *promise = [COPromise promise ];
[ self GET: url parameters: parameters progress: nil success: ^( NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[promise fulfill: responseObject];
} failure: ^( NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[promise reject: error];
}];
return promise;
}ثم يمكننا استخدام الطريقة في coroutine :
co_launch (^{
id response = await ([ self co_GET: feedModel.feedUrl parameters: nil ]);
if ( co_getError ()){
// handle error message
}
...
});نستخدم co_sequence لإنشاء المولد
COCoroutine *co1 = co_sequence(^{
int index = 0 ;
while ( co_isActive ()){
yield_val (@( index ));
index ++;
}
});في Coroutines الأخرى ، يمكننا الاتصال بالطريقة التالية للحصول على البيانات في المولد.
co_launch (^{
for ( int i = 0 ; i < 10 ; i++){
val = [[co1 next ] intValue ];
}
});يمكن استخدام المولد في العديد من السيناريوهات ، مثل قوائم قوائم الرسائل ، وملفات تنزيل الدُفعات ، وذاكرة التخزين المؤقت للتحميل بالجملة ، وما إلى ذلك:
int unreadMessageCount = 10 ;
NSString *userId = @" xxx " ;
COSequence *messageSequence = co_sequence_onqueue(background_queue, ^{
// thread execution in the background
while ( 1 ){
yield ( queryOneNewMessageForUserWithId (userId));
}
});
// Main thread update UI
co_launch (^{
for ( int i = 0 ; i < unreadMessageCount; i++){
if (! isQuitCurrentView ()){
displayMessage ([messageSequence next ]);
}
}
});من خلال المولد ، يمكننا تحميل البيانات من المنتج التقليدي-عدم تحديد نموذج المستهلك ، وتحويل المستهلك إلى البيانات-> إخبار المنتج بتحميل النمط ، وتجنب الحاجة إلى استخدام العديد من المتغيرات المشتركة للحالة في الحوسبة متعددة الخيوط. التزامن يلغي استخدام الأقفال في سيناريوهات معينة.
مفهوم الممثل يأتي من إرلانج. في Akka ، يمكن اعتبار الممثل كحاوية لتخزين سياسات الحالة والسلوك وصندوق البريد وممثل الأطفال وسياسات المشرف. الجهات الفاعلة لا تتواصل مباشرة ، ولكن استخدام البريد للتواصل مع بعضها البعض.
يمكننا استخدام co_actor_onqueue لإنشاء ممثل في الخيط المحدد.
COActor *actor = co_actor_onqueue(q, ^(COActorChan *channel) {
... // Define the state variable of the actor
for (COActorMessage *message in channel){
... // handle message
}
});يمكن أن ترسل طريقة إرسال الممثل رسالة إلى الممثل
COActor *actor = co_actor_onqueue(q, ^(COActorChan *channel) {
... // Define the state variable of the actor
for (COActorMessage *message in channel){
... // handle message
}
});
// send a message to the actor
[actor send: @" sadf " ];
[actor send: @( 1 )];COTuple *tup = co_tuple( nil , @ 10 , @" abc " );
NSAssert (tup[ 0 ] == nil , @" tup[0] is wrong " );
NSAssert ([tup[ 1 ] intValue ] == 10 , @" tup[1] is wrong " );
NSAssert ([tup[ 2 ] isEqualToString: @" abc " ], @" tup[2] is wrong " );يمكنك تخزين أي قيمة في Tuple
id val0;
NSNumber *number = nil ;
NSString *str = nil ;
co_unpack (&val0, &number, &str) = co_tuple( nil , @ 10 , @" abc " );
NSAssert (val0 == nil , @" val0 is wrong " );
NSAssert ([number intValue ] == 10 , @" number is wrong " );
NSAssert ([str isEqualToString: @" abc " ], @" str is wrong " );
co_unpack (&val0, &number, &str) = co_tuple( nil , @ 10 , @" abc " , @ 10 , @" abc " );
NSAssert (val0 == nil , @" val0 is wrong " );
NSAssert ([number intValue ] == 10 , @" number is wrong " );
NSAssert ([str isEqualToString: @" abc " ], @" str is wrong " );
co_unpack (&val0, &number, &str, &number, &str) = co_tuple( nil , @ 10 , @" abc " );
NSAssert (val0 == nil , @" val0 is wrong " );
NSAssert ([number intValue ] == 10 , @" number is wrong " );
NSAssert ([str isEqualToString: @" abc " ], @" str is wrong " );
NSString *str1;
co_unpack ( nil , nil , &str1) = co_tuple( nil , @ 10 , @" abc " );
NSAssert ([str1 isEqualToString: @" abc " ], @" str1 is wrong " );COPromise<COTuple*>*
cotest_loadContentFromFile ( NSString *filePath){
return [COPromise promise: ^(COPromiseFullfill _Nonnull resolve, COPromiseReject _Nonnull reject) {
if ([[ NSFileManager defaultManager ] fileExistsAtPath: filePath]) {
NSData *data = [[ NSData alloc ] initWithContentsOfFile: filePath];
resolve ( co_tuple (filePath, data, nil ));
}
else {
NSError *error = [ NSError errorWithDomain: @" fileNotFound " code: - 1 userInfo: nil ];
resolve ( co_tuple (filePath, nil , error));
}
}];
}ثم يمكنك جلب القيمة مثل هذه:
co_launch (^{
NSString *tmpFilePath = nil ;
NSData *data = nil ;
NSError *error = nil ;
co_unpack (&tmpFilePath, &data, &error) = await ( cotest_loadContentFromFile (filePath));
XCTAssert ([tmpFilePath isEqualToString: filePath], @" file path is wrong " );
XCTAssert (data. length > 0 , @" data is wrong " );
XCTAssert (error == nil , @" error is wrong " );
});استخدم tuple يمكنك الحصول على قيم متعددة من انتظار الإرجاع
دعنا نأخذ رمز تحديث دفق التغذية في مشروع GCDFetchFeed Open Source كمثال لإظهار سيناريوهات الاستخدام الفعلي ومزايا Coroutine. ما يلي هو التنفيذ الأصلي لعدم استخدام coroutine :
- (RACSignal *)fetchAllFeedWithModelArray:( NSMutableArray *)modelArray {
@ weakify (self);
return [RACSignal createSignal: ^RACDisposable *( id <RACSubscriber> subscriber) {
@ strongify (self);
// Create a parallel queue
dispatch_queue_t fetchFeedQueue = dispatch_queue_create ( " com.starming.fetchfeed.fetchfeed " , DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create ();
self. feeds = modelArray;
for ( int i = 0 ; i < modelArray. count ; i++) {
dispatch_group_enter (group);
SMFeedModel *feedModel = modelArray[i];
feedModel. isSync = NO ;
[ self GET: feedModel.feedUrl parameters: nil progress: nil success: ^( NSURLSessionTask *task, id responseObject) {
dispatch_async (fetchFeedQueue, ^{
@ strongify (self);
// parse feed
self. feeds [i] = [ self .feedStore updateFeedModelWithData: responseObject preModel: feedModel];
// save to db
SMDB *db = [SMDB shareInstance ];
@ weakify (db);
[[db insertWithFeedModel: self .feeds[i]] subscribeNext: ^( NSNumber *x) {
@ strongify (db);
SMFeedModel *model = (SMFeedModel *)self. feeds [i];
model. fid = [x integerValue ];
if (model. imageUrl . length > 0 ) {
NSString *fidStr = [x stringValue ];
db. feedIcons [fidStr] = model. imageUrl ;
}
// sendNext
[subscriber sendNext: @(i)];
// Notification single completion
dispatch_group_leave (group);
}];
}); // end dispatch async
} failure: ^( NSURLSessionTask *operation, NSError *error) {
NSLog ( @" Error: %@ " , error);
dispatch_async (fetchFeedQueue, ^{
@ strongify (self);
[[[SMDB shareInstance ] insertWithFeedModel: self .feeds[i]] subscribeNext: ^( NSNumber *x) {
SMFeedModel *model = (SMFeedModel *)self. feeds [i];
model. fid = [x integerValue ];
dispatch_group_leave (group);
}];
}); // end dispatch async
}];
} // end for
// Execution event after all is completed
dispatch_group_notify (group, dispatch_get_main_queue (), ^{
[subscriber sendCompleted ];
});
return nil ;
}];
}فيما يلي دعوة إلى الطريقة أعلاه في ViewDidload:
[UIApplication sharedApplication ].networkActivityIndicatorVisible = YES ;
self.fetchingCount = 0 ;
@weakify(self);
[[[[[[SMNetManager shareInstance ] fetchAllFeedWithModelArray: self .feeds] map: ^ id ( NSNumber *value) {
@ strongify (self);
NSUInteger index = [value integerValue ];
self. feeds [ index ] = [SMNetManager shareInstance ]. feeds [ index ];
return self. feeds [ index ];
}] doCompleted: ^{
@ strongify (self);
NSLog ( @" fetch complete " );
self. tbHeaderLabel . text = @" " ;
self. tableView . tableHeaderView = [[UIView alloc ] init ];
self. fetchingCount = 0 ;
[ self .tableView.mj_header endRefreshing ];
[ self .tableView reloadData ];
if ([SMFeedStore defaultFeeds ]. count > self. feeds . count ) {
self. feeds = [SMFeedStore defaultFeeds ];
[ self fetchAllFeeds ];
}
[ self cacheFeedItems ];
}] deliverOn: [RACScheduler mainThreadScheduler ]] subscribeNext: ^(SMFeedModel *feedModel) {
@ strongify (self);
self. tableView . tableHeaderView = self. tbHeaderView ;
self. fetchingCount += 1 ;
self. tbHeaderLabel . text = [ NSString stringWithFormat: @"正在获取%@ ...( %lu / %lu ) " ,feedModel.title,( unsigned long ) self .fetchingCount,( unsigned long ) self .feeds.count];
feedModel. isSync = YES ;
[ self .tableView reloadData ];
}];الكود أعلاه ضعيف نسبيا من حيث قابلية القراءة والبساطة. دعونا نلقي نظرة على الكود بعد استخدام تحويل coroutine:
- (SMFeedModel*)co_fetchFeedModelWithUrl:(SMFeedModel*)feedModel{
feedModel. isSync = NO ;
id response = await ([ self co_GET: feedModel.feedUrl parameters: nil ]);
if (response) {
SMFeedModel *resultModel = await ([ self co_updateFeedModelWithData: response preModel: feedModel]);
int fid = [[SMDB shareInstance ] co_insertWithFeedModel: resultModel];
resultModel. fid = fid;
if (resultModel. imageUrl . length > 0 ) {
NSString *fidStr = [@(fid) stringValue ];
[SMDB shareInstance ]. feedIcons [fidStr] = resultModel. imageUrl ;
}
return resultModel;
}
int fid = [[SMDB shareInstance ] co_insertWithFeedModel: feedModel];
feedModel. fid = fid;
return nil ;
}فيما يلي المكان الذي يستخدم فيه coroutine لاستدعاء الواجهة:
co_launch (^{
for ( NSUInteger index = 0 ; index < self. feeds . count ; index ++) {
SMFeedModel *model = self. feeds [ index ];
self. tableView . tableHeaderView = self. tbHeaderView ;
self. tbHeaderLabel . text = [ NSString stringWithFormat: @"正在获取%@ ...( %lu / %lu ) " ,model.title,( unsigned long )( index + 1 ),( unsigned long ) self .feeds.count];
model. isSync = YES ;
SMFeedModel *resultMode = [[SMNetManager shareInstance ] co_fetchFeedModelWithUrl: model];
if (resultMode) {
self. feeds [ index ] = resultMode;
[ self .tableView reloadData ];
}
}
self. tbHeaderLabel . text = @" " ;
self. tableView . tableHeaderView = [[UIView alloc ] init ];
self. fetchingCount = 0 ;
[ self .tableView.mj_header endRefreshing ];
[ self .tableView reloadData ];
[ self cacheFeedItems ];
});أصبح الكود بعد تحويل coroutine أسهل في الفهم وأقل عرضة للخطأ.
يدعم Coobjc بالكامل سريعًا من خلال تغليف المستوى الأعلى ، مما يتيح لنا الاستمتاع بـ Coroutine في وقت مبكر في Swift. نظرًا لأن Swift لديه دعم بناء جملة أكثر ثراءً وأكثر تقدمًا ، فإن CoobJC أكثر أناقة في Swift ، على سبيل المثال:
func test ( ) {
co_launch { //create coroutine
//fetch data asynchronous
let resultStr = try await ( channel : co_fetchSomething ( ) )
print ( " result: ( resultStr ) " )
}
co_launch { //create coroutine
//fetch data asynchronous
let result = try await ( promise : co_fetchSomethingAsynchronous ( ) )
switch result {
case . fulfilled ( let data ) :
print ( " data: ( String ( describing : data ) ) " )
break
case . rejected ( let error ) :
print ( " error: ( error ) " )
}
}
} يتضمن COOBJC مجموعة من اختبارات الوحدة ضمن الاختبارات الفرعية. يمكن تشغيل هذه الاختبارات ببساطة ، يتم تنفيذ إجراء الاختبار على إطار النظام الأساسي الذي ترغب في اختباره. يمكنك العثور على اختبارات وحدة COOBJC في أمثلة/COOBJCBASEEXAMPLE/COOBJCBASEEXAMPLETESTS. يمكنك العثور على اختبارات وحدة Cokit في Cokit/أمثلة/cokitexamples/cokitexamplestests.
لا يمكن أن يوجد coobjc بدون:
يتم إصدار COOBJC بموجب ترخيص Apache 2.0. انظر الترخيص للحصول على التفاصيل.