
Diese Bibliothek bietet Coroutine-Unterstützung für Objektiv-C und Swift. Wir haben erwartet eine Methode 、 Generator und Schauspielermodell wie C#、 JavaScript und Kotlin. Aus Gründen der Bequemlichkeit haben wir Coroutine -Kategorien für eine Fundament- und Uikit -API im Cokit -Framework wie NSFilemanager, JSON, NSDATA, UIImage usw. hinzugefügt. Wir fügen auch Tuple -Unterstützung in COOBJC hinzu.
cooobjc 中文文档
Blockbasierter asynchroner Programmierruf ist derzeit die am häufigsten verwendete asynchrone Programmiermethode für iOS. Die vom iOS -System bereitgestellte GCD -Bibliothek macht eine asynchrone Entwicklung sehr einfach und bequem, aber es gibt viele Nachteile, die auf dieser Programmiermethode basieren:
Machen Sie sich in die Callback -Hölle
Die Sequenz einfacher Operationen ist in den verschachtelten Blöcken unnatürlich komponiert. Diese "Callback -Hölle" macht es schwierig, den Laufen Code zu verfolgen, und der Stapel von Schließungen führt zu vielen Effekten zweiter Ordnung.
Handhabungsfehler werden schwierig und sehr ausführlich
Die bedingte Ausführung ist hart und fehleranfällig
Vergessen Sie, den Abschlussblock anzurufen
Da die Fertigstellung von Handlern umständlich ist, werden zu viele APIs synchron definiert
Dies ist schwer zu quantifizieren, aber die Autoren glauben, dass die Unbeholfenheit der Definition und Verwendung asynchroner APIs (mithilfe von Abschlusshandlern) dazu geführt hat, dass viele APIs mit scheinbar synchronem Verhalten definiert werden, auch wenn sie blockieren können. Dies kann zu problematischen Leistung und Reaktionsfähigkeitsproblemen in UI -Anwendungen führen - z. B. Spinning Cursor. Es kann auch zu der Definition von APIs führen, die nicht verwendet werden kann, wenn Asynchronität für die Erzielung von Skalieren, z. B. auf dem Server, von entscheidender Bedeutung ist.
Multi-Threaden-Abstürze, die schwer zu lokalisieren sind
Schlösser und Semaphormissbrauch durch Blockierung verursacht
Dieses Problem wurde in vielen Systemen und vielen Sprachen konfrontiert, und die Abstraktion von Coroutines ist eine Standardmethode, um sie anzugehen. Coroutinen sind eine Erweiterung grundlegender Funktionen, die es einer Funktion ermöglichen, einen Wert zurückzugeben oder suspendiert zu werden. Sie können verwendet werden, um Generatoren, asynchrone Modelle und andere Fähigkeiten zu implementieren - es gibt eine große Anzahl von Arbeiten zur Theorie, Implementierung und Optimierung von ihnen.
Kotlin ist eine statische Programmiersprache, die von JetBrains unterstützt wird und moderne Multi-Plattform-Anwendungen unterstützt. In den letzten zwei Jahren war es in der Entwicklergemeinschaft ziemlich heiß. In der Kotlin -Sprache, asynchronen/wartet auf Basis von Coroutine, Generator/Ertrag und anderen asynchronen Technologien, sind zu syntaktischer Standard geworden. Kotlin Coroutine Inhaltte Einführung können Sie unter: https: //www.kotlincn.net/docs/reference/coroutines/basics.htm.htm..htm.
Coroutinen sind Computerprogrammkomponenten, die Unterprogramme für nicht preemptive Multitasking verallgemeinern, indem die Ausführung suspendiert und wieder aufgenommen wird. Coroutinen eignen sich gut für die Implementierung bekannter Programmkomponenten wie kooperative Aufgaben, Ausnahmen, Ereignisschleifen, Iteratoren, unendlichen Listen und Rohre
Das Konzept der Coroutine wurde in den 1960er Jahren vorgeschlagen. Es wird im Server häufig verwendet. Es ist sehr geeignet für die Verwendung in hohen Parallelitätsszenarien. Es kann die Anzahl der Fäden in einer einzelnen Maschine erheblich reduzieren und die Verbindungs- und Verarbeitungsfunktionen einer einzelnen Maschine verbessern. In der Zwischenzeit unterstützt iOS derzeit nicht die Verwendung von Coroutinen. Deshalb möchten wir es unterstützen.)
COOBJC ist ein Coroutine-Entwicklungsrahmen, das vom Alibaba Taobao-Mobile Architecture-Team auf dem iOS verwendet werden kann. Derzeit unterstützt es die Verwendung von Objective-C und Swift. Wir verwenden die Montage- und C-Sprache für die Entwicklung, und die obere Schicht bietet die Schnittstelle zwischen Objektiv-C und Swift. Derzeit ist es Open Source hier unter Apache Open Source -Lizenz.
Erstellen Sie mit der CO_Launch -Methode eine Coroutine
co_launch (^{
...
});Die von Co_Launch erstellte Coroutine ist standardmäßig im aktuellen Thread geplant.
In der Coroutine verwenden wir die wartete Methode, um auf die asynchrone Methode zu warten, um das asynchrone Ausführungsergebnis zu erhalten
- ( 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;
});
}Der obige Code verwandelt den Code, der ursprünglich dispatch_async zweimal in sequentielle Ausführung benötigt, und der Code ist prägnanter.
In der Coroutine geben alle unsere Methoden den Wert direkt zurück, und es wird kein Fehler zurückgegeben. Unser Fehler im Ausführungsprozess wird von Co_GetError () erhalten. Zum Beispiel haben wir die folgende Schnittstelle, um Daten aus dem Netzwerk zu erhalten. Wenn das Versprechen abgelehnt wird: Fehler
- (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;
}Dann können wir die Methode in der Coroutine verwenden:
co_launch (^{
id response = await ([ self co_GET: feedModel.feedUrl parameters: nil ]);
if ( co_getError ()){
// handle error message
}
...
});Wir verwenden Co_Sequence, um den Generator zu erstellen
COCoroutine *co1 = co_sequence(^{
int index = 0 ;
while ( co_isActive ()){
yield_val (@( index ));
index ++;
}
});In anderen Coroutinen können wir die nächste Methode aufrufen, um die Daten im Generator zu erhalten.
co_launch (^{
for ( int i = 0 ; i < 10 ; i++){
val = [[co1 next ] intValue ];
}
});Der Generator kann in vielen Szenarien verwendet werden, wie z. B. Nachrichtenwarteschlangen, Batch -Download -Dateien, Bulk -Lade -Caches usw .:
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 ]);
}
}
});Durch den Generator können wir die Daten vom herkömmlichen Hersteller laden-das Verbrauchermodell nicht zu verwenden, den Verbraucher in die Daten zu verwandeln-> fordert den Produzenten auf, das Muster zu laden, und vermeiden Sie, dass viele gemeinsam genutzte Variablen für den Status im Multi-Thread-Computer verwendet werden müssen. Die Synchronisation beseitigt die Verwendung von Sperren in bestimmten Szenarien.
Das Konzept des Schauspielers stammt aus Erlang. In Akka kann ein Schauspieler als Container für die Speicherung von Status-, Verhaltens-, Postfach-, Kinder- und Vorgesetzten -Richtlinien betrachtet werden. Akteure kommunizieren nicht direkt, sondern verwenden E -Mails, um miteinander zu kommunizieren.
Wir können co_actor_onqueue verwenden, um einen Schauspieler im angegebenen Thread zu erstellen.
COActor *actor = co_actor_onqueue(q, ^(COActorChan *channel) {
... // Define the state variable of the actor
for (COActorMessage *message in channel){
... // handle message
}
});Die Sendungsmethode des Schauspielers kann eine Nachricht an den Schauspieler senden
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 " );Sie können jeden Wert in Tuple speichern
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));
}
}];
}Dann können Sie den Wert wie diesen abrufen:
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 " );
});Verwenden Sie Tuple Sie können mehrere Werte von Warte -Return erhalten
Nehmen wir den Code des Feeds -Stream -Updates im GCDFetchFetchFeed Open Source -Projekt als Beispiel, um die tatsächlichen Nutzungsszenarien und Vorteile der Coroutine zu demonstrieren. Das Folgende ist die ursprüngliche Implementierung der Nicht -Verwendung von 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 ;
}];
}Das Folgende ist der Aufruf zur oben genannten Methode in 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 ];
}];Der obige Code ist hinsichtlich Lesbarkeit und Einfachheit relativ schlecht. Schauen wir uns den Code nach der Verwendung der Coroutine -Transformation an:
- (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 ;
}Hier ist der Ort in ViewDIdload, an dem die Coroutine die Schnittstelle aufgerufen wird:
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 ];
});Der Code nach der Coroutine-Transformation ist einfacher zu verstehen und weniger fehleranfällig.
COOBJC unterstützt Swift durch die Kapselung der obersten Ebene und ermöglicht es uns, die Coroutine im Voraus in Swift zu genießen. Da Swift eine reichhaltigere und fortschrittlichere Syntaxunterstützung hat, ist COOBJC beispielsweise eleganter in 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 enthält eine Reihe von Unit -Tests innerhalb des Tests der Tests. Diese Tests können einfach ausgeführt werden. Die Testaktionen im Plattform -Framework, das Sie testen möchten, können ausgeführt werden. Sie finden die Unit -Tests von COOBJC in Beispielen/CoobjcBaseExample/coobjcBasexampletests. Sie finden die Unit -Tests von Cokit in Cokit/Beispielen/Cokitexamples/cokitexamplestests.
Coobjc konnte nicht existieren, ohne:
COOBJC wird unter der Apache 2.0 -Lizenz veröffentlicht. Weitere Informationen finden Sie in Lizenz.