
このライブラリは、Objective-CとSwiftのCoroutineサポートを提供します。 C#、JavaScriptやKotlinのようなAwait Methors、ジェネレーターと俳優モデルを追加しました。便利なため、NSFILEMANAGER、JSON、NSDATA、UIIMAGEなどのCokitフレームワークに、いくつかのファンデーションとUIKIT APIのCoroutineカテゴリを追加しました。COOBJCにタプルサポートも追加します。
cooobjc中文文档
ブロックベースの非同期プログラミングコールバックは、現在、iOSで最も広く使用されている非同期プログラミング方法です。 iOSシステムが提供するGCDライブラリは、非同期開発を非常にシンプルで便利にしますが、このプログラミング方法に基づいて多くの欠点があります。
コールバックヘルに入ります
単純な操作のシーケンスは、ネストされたブロックで不自然に構成されています。この「コールバックヘル」により、実行中のコードを追跡することが困難になり、閉鎖のスタックは多くの2次効果につながります。
取り扱いエラーは困難になり、非常に冗長になります
条件付き実行は困難でエラーが発生しやすいです
完了ブロックを呼び出すのを忘れてください
完了ハンドラーは厄介であるため、あまりにも多くのAPIが同期して定義されています
これは定量化するのが難しいですが、著者は、非同期API(完了ハンドラーを使用)を定義して使用することの厄介さが、ブロックできる場合でも、明らかに同期的な動作で多くのAPIが定義されるようになったと考えています。これにより、UIアプリケーションで問題のあるパフォーマンスと応答性の問題が発生する可能性があります。例:スピニングカーソルです。また、非同期がサーバー上でスケールを達成するために重要である場合に使用できないAPIの定義につながる可能性があります。
見つけるのが難しいマルチスレッドクラッシュ
ブロッキングによって引き起こされるロックとセマフォの乱用
これらの問題は多くのシステムや多くの言語で直面しており、コルーチンの抽象化はそれらに対処するための標準的な方法です。理論をあまり掘り下げることなく、コルーチンは、関数が値を返すか、中断できるようにする基本的な関数の拡張です。それらを使用して、発電機、非同期モデル、およびその他の機能を実装できます。それらの理論、実装、および最適化には多くの作業があります。
Kotlinは、最新のマルチプラットフォームアプリケーションをサポートするジェットブレインでサポートされている静的なプログラミング言語です。過去2年間、開発者コミュニティでは非常に暑かったです。 Kotlin言語では、Coroutine、Generator/Hightおよびその他の非同期技術に基づくAsync/async/async/async/async/https://www.kotlincn.net/docs/docs/reference/coroutines/basics.htmllを参照できます。
Coroutinesは、実行を中断して再開できるようにすることにより、非寛容なマルチタスクのサブルーチンを一般化するコンピュータープログラムコンポーネントです。 Coroutinesは、協力タスク、例外、イベントループ、イテレーター、無限のリスト、パイプなどの馴染みのあるプログラムコンポーネントを実装するのに適しています
コルーチンの概念は1960年代に提案されました。サーバーで広く使用されています。高い並行性シナリオでの使用に非常に適しています。単一のマシンのスレッドの数を大幅に削減し、単一のマシンの接続と処理機能を改善できます。それまでの間、iOSは現在、コルーチンの使用をサポートしていません。それが私たちがそれをサポートしたい理由です。
COOBJCは、Alibaba Taobao-Mobile ArchitectureチームがiOSで使用できるコルーチン開発フレームワークです。現在、Objective-CとSwiftの使用をサポートしています。開発にはアセンブリとC言語を使用し、上層層はObjective-CとSwiftの間のインターフェイスを提供します。現在、これはApache Open Sourceライセンスの下にあるオープンソースです。
Co_launchメソッドを使用してコルーチンを作成します
co_launch (^{
...
});Co_launchによって作成されたCoroutineは、現在のスレッドでデフォルトでスケジュールされています。
Coroutineでは、Await Methodを使用して、非同期メソッドが実行されるのを待つ、非同期実行結果を取得する
- ( 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が2回順次実行される必要があるコードを順番に並べており、コードはより簡潔になります。
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 ++;
}
});他のコルーチンでは、次の方法を呼び出して、ジェネレーターにデータを取得できます。
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 " );あらゆる価値をタプルに保存できます
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 " );
});タプルを使用すると、waint returnから複数の値を取得できます
Coroutineの実際の使用シナリオと利点を実証するための例として、GCDFetchFeed Open SourceプロジェクトのFeeds Streamアップデートのコードを取得しましょう。以下は、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 ];
}];上記のコードは、読みやすさとシンプルさの点で比較的貧弱です。コルーチン変換を使用した後、コードを見てみましょう。
- (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の場所です。
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にはより豊かで高度な構文サポートがあるため、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/Examples/cokitexamples/cokitexamplestesで見つけることができます。
COOBJCなしでは存在できませんでした:
COOBJCは、Apache 2.0ライセンスの下でリリースされます。詳細については、ライセンスを参照してください。