
ห้องสมุดนี้ให้การสนับสนุน coroutine สำหรับ Objective-C และ Swift เราเพิ่มวิธีการรอ、 เครื่องกำเนิดไฟฟ้าและนักแสดงเช่น C#、 JavaScript และ Kotlin เพื่อความสะดวกเราได้เพิ่มหมวดหมู่ coroutine สำหรับรากฐานและ Uikit API ในกรอบ Cokit เช่น NSFileManager, JSON, NSDATA, UIIMAGE เป็นต้น
cooobjc 中文文档
การโทรกลับการเขียนโปรแกรมแบบอะซิงโครนัสแบบบล็อกเป็นวิธีการเขียนโปรแกรมแบบอะซิงโครนัสที่ใช้กันอย่างแพร่หลายมากที่สุดสำหรับ iOS ห้องสมุด GCD ที่จัดทำโดยระบบ iOS ทำให้การพัฒนาแบบอะซิงโครนัสง่ายและสะดวกมาก แต่มีข้อเสียมากมายตามวิธีการเขียนโปรแกรมนี้:
เข้าสู่การโทรกลับนรก
ลำดับของการดำเนินการอย่างง่ายนั้นประกอบขึ้นอย่างไม่เป็นธรรมชาติในบล็อกซ้อนกัน "นรกโทรกลับ" นี้ทำให้ยากต่อการติดตามรหัสที่กำลังทำงานอยู่และการปิดของการปิดนำไปสู่เอฟเฟกต์ลำดับที่สอง
ข้อผิดพลาดในการจัดการกลายเป็นเรื่องยากและมีคำย้ำมาก
การดำเนินการตามเงื่อนไขนั้นยากและผิดพลาดได้ง่าย
ลืมโทรไปที่บล็อกความสำเร็จ
เนื่องจากตัวจัดการที่เสร็จสมบูรณ์นั้นอึดอัด
นี่เป็นเรื่องยากที่จะหาปริมาณ แต่ผู้เขียนเชื่อว่าความอึดอัดใจของการกำหนดและการใช้ API แบบอะซิงโครนัส (โดยใช้ตัวจัดการที่สมบูรณ์) ได้นำไปสู่ APIs จำนวนมากที่ถูกกำหนดด้วยพฤติกรรมแบบซิงโครนัสที่เห็นได้ชัดแม้ว่าพวกเขาจะบล็อก สิ่งนี้สามารถนำไปสู่ปัญหาประสิทธิภาพและปัญหาการตอบสนองในแอปพลิเคชัน UI - เช่นเคอร์เซอร์ปั่น นอกจากนี้ยังสามารถนำไปสู่คำจำกัดความของ APIs ที่ไม่สามารถใช้งานได้เมื่ออะซินโคนมีความสำคัญต่อการบรรลุมาตราส่วนเช่นบนเซิร์ฟเวอร์
การล่มแบบมัลติเธรดที่ยากต่อการค้นหา
ล็อคและการละเมิดสัญญาณที่เกิดจากการปิดกั้น
ปัญหาเหล่านี้ต้องเผชิญในหลาย ๆ ระบบและหลายภาษาและสิ่งที่เป็นนามธรรมของ coroutines เป็นวิธีมาตรฐานในการจัดการกับพวกเขา โดยไม่ต้องเจาะลึกลงไปในทฤษฎีมากเกินไป coroutines เป็นส่วนขยายของฟังก์ชั่นพื้นฐานที่อนุญาตให้ฟังก์ชันส่งคืนค่าหรือถูกระงับ พวกเขาสามารถใช้ในการใช้เครื่องกำเนิดไฟฟ้าแบบอะซิงโครนัสและความสามารถอื่น ๆ - มีงานขนาดใหญ่เกี่ยวกับทฤษฎีการใช้งานและการเพิ่มประสิทธิภาพของพวกเขา
Kotlin เป็นภาษาการเขียนโปรแกรมแบบคงที่ที่สนับสนุนโดย Jetbrains ที่รองรับแอปพลิเคชันหลายแพลตฟอร์มที่ทันสมัย มันค่อนข้างร้อนแรงในชุมชนนักพัฒนาในช่วงสองปีที่ผ่านมา ในภาษา Kotlin, async/รออยู่บนพื้นฐานของ coroutine, เครื่องกำเนิด/ผลผลิตและเทคโนโลยีอะซิงโครนัสอื่น ๆ ได้กลายเป็นมาตรฐานวากยสัมพันธ์, kotlin coroutine บทนำที่เกี่ยวข้อง
Coroutines เป็นส่วนประกอบของโปรแกรมคอมพิวเตอร์ที่สรุปรูทีนย่อยสำหรับการทำงานหลายอย่างที่ไม่ได้รับการยกเว้นโดยอนุญาตให้มีการดำเนินการและดำเนินการต่อ Coroutines เหมาะอย่างยิ่งสำหรับการใช้งานส่วนประกอบของโปรแกรมที่คุ้นเคยเช่นงานความร่วมมือข้อยกเว้นลูปเหตุการณ์ตัววนซ้ำรายการที่ไม่มีที่สิ้นสุดและท่อ
แนวคิดของ coroutine ได้รับการเสนอในปี 1960 มีการใช้กันอย่างแพร่หลายในเซิร์ฟเวอร์ เหมาะอย่างยิ่งสำหรับการใช้งานในสถานการณ์ที่เกิดขึ้นพร้อมกันสูง มันสามารถลดจำนวนเธรดในเครื่องเดียวได้อย่างมากและปรับปรุงความสามารถในการเชื่อมต่อและการประมวลผลของเครื่องเดียว ในระหว่างนี้ iOS ปัจจุบันไม่สนับสนุนการใช้ coroutines (นั่นคือเหตุผลที่เราต้องการสนับสนุนมัน)
Coobjc เป็นกรอบการพัฒนา coroutine ที่สามารถใช้กับ iOS โดยทีมงานสถาปัตยกรรมอาลีบาบา Taobao-Mobile ปัจจุบันรองรับการใช้ Objective-C และ Swift เราใช้ภาษาแอสเซมบลีและ C เพื่อการพัฒนาและชั้นบนให้อินเทอร์เฟซระหว่าง Objective-C และ Swift ปัจจุบันเป็นโอเพ่นซอร์สที่นี่ภายใต้ใบอนุญาตโอเพนซอร์ส Apache
สร้าง 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_equence เพื่อสร้างเครื่องกำเนิดไฟฟ้า
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 ]);
}
}
});ผ่านเครื่องกำเนิดไฟฟ้าเราสามารถโหลดข้อมูลจากผู้ผลิตแบบดั้งเดิม-การแจ้งรูปแบบผู้บริโภคเปลี่ยนผู้บริโภคให้เป็นข้อมูล-> บอกผู้ผลิตให้โหลดรูปแบบหลีกเลี่ยงความจำเป็นในการใช้ตัวแปรที่ใช้ร่วมกันจำนวนมากสำหรับรัฐในการคำนวณแบบหลายเธรด การซิงโครไนซ์ช่วยลดการใช้ล็อคในบางสถานการณ์
แนวคิดของนักแสดงมาจาก Erlang ใน 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 เป็นตัวอย่างเพื่อแสดงให้เห็นถึงสถานการณ์การใช้งานจริงและข้อดีของ 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 ;
}นี่คือสถานที่ใน ViewDidLoad ที่ใช้ 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 รองรับ Swift ผ่านการห่อหุ้มระดับบนสุดอย่างเต็มที่ทำให้เราสามารถเพลิดเพลินไปกับ 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 ดูใบอนุญาตสำหรับรายละเอียด