15

インターネットからいくつかのオブジェクト情報を取得するためのメソッドがあります。

- (void)downloadAppInfo:(void(^)())success
                failure:(void(^)(NSError *error))failure;
- (void)getAvailableHosts:(void(^)())success
                  failure:(void(^)(NSError *error))failure;
- (void)getAvailableServices:(void(^)())success
                     failure:(void(^)(NSError *error))failure;
- (void)getAvailableActions:(void(^)())success
                    failure:(void(^)(NSError *error))failure;

ダウンロードされたものはオブジェクトのプロパティに保存されるため、成功関数は何も返しません。

今、私は次のような1つの方法を持ちたいです:

- (void)syncEverything:(void(^)())success
               failure:(void(^)(NSError *error))failure;

上記のすべてのメソッドを呼び出し、すべてのメソッドが成功ブロックまたは失敗ブロックを実行した後にのみ戻るだけです。

これどうやってするの?

ヒント: 成功ブロックごとにメソッド呼び出しをカスケードするとうまくいくことは承知しています。しかし、これは「クリーン」でもなく、後の実装にさらにメソッドが含まれる場合にも役に立ちません。

試み:

各呼び出しを で実行し、それらを にNSOperation追加してから、前の操作のすべてに依存する「完了操作」を試みました。NSOperationsNSOperationQueue

これはうまくいきません。それぞれの成功/失敗ブロックが返される前であっても、操作は完了したと見なされるためです。

も使ってみdispatch_groupました。しかし、私がそれを正しい方法で行っているかどうかは、私には明らかではありません。残念ながら、それは機能していません。

4

5 に答える 5

17

ここの他の回答のコメントとブログ投稿Using dispatch groups to wait for multiple web servicesから引き出され、次の回答にたどり着きました。

このソリューションでは、 と を使用dispatch_group_enterdispatch_group_leaveて、各中間タスクがいつ実行されているかを判断します。すべてのタスクが完了すると、最後のdispatch_group_notifyブロックが呼び出されます。その後、すべての中間タスクが終了したことがわかっているので、完了ブロックを呼び出すことができます。

dispatch_group_t group = dispatch_group_create();

dispatch_group_enter(group);
[self yourBlockTaskWithCompletion:^(NSString *blockString) {

    // ...

    dispatch_group_leave(group);
}];

dispatch_group_enter(group);
[self yourBlockTaskWithCompletion:^(NSString *blockString) {

    // ...

    dispatch_group_leave(group);
}];

dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{

    // All group blocks have now completed

    if (completion) {
        completion();
    }
});

グランド セントラル ディスパッチ - ディスパッチ グループ

https://developer.apple.com/documentation/dispatch/dispatchgroup

ブロックをグループ化すると、集計の同期が可能になります。アプリケーションは複数のブロックを送信し、それらが異なるキューで実行される場合でも、それらがすべて完了したことを追跡できます。この動作は、指定されたすべてのタスクが完了するまで進行できない場合に役立ちます。

Xcode スニペット:

コードに簡単に挿入できるように、次のコードを Xcode スニペットとして追加しました

入力DISPATCH_SETすると、次のコードが挿入されます。次に、非同期ブロックごとにenter/をコピーして貼り付けます。leave

目的 C:

dispatch_group_t group = dispatch_group_create();

dispatch_group_enter(group);

dispatch_group_leave(group);

dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{

});

迅速:

let dispatchGroup = DispatchGroup()

dispatchGroup.enter()

dispatchGroup.leave()

dispatchGroup.notify(queue: .global()) {

}
于 2016-04-13T19:54:25.810 に答える
2

もう 1 つの解決策は、いくつかのサードパーティ ライブラリで利用できるPromiseを使用することです。私はPromises/A+ 仕様を実装するRXPromiseの作成者です。

しかし、他に少なくとも 2 つの Objective-C 実装があります。

Promise は、非同期メソッドまたは操作の最終的な結果を表します。

-(Promise*) doSomethingAsync;

promise は、完了ハンドラを完全に置き換えるものです。さらに、その明確な仕様と基礎となる設計により、非常に便利な機能がいくつかあり、かなり複雑な非同期問題を特に簡単に処理できます。

最初に行う必要があるのは、完了ハンドラーを使用して非同期メソッドをPromise を返す非同期メソッドにラップすることです: (意図的に、メソッドは最終的な結果潜在的なエラーをより便利な完了ハンドラーで返します)

例えば:

- (RXPromise*) downloadAppInfo {
    RXPromise* promise = [RXPromise new];
    [self downloadAppInfoWithCompletion:^(id result, NSError *error) {
        if (error) {
            [promise rejectWithReason:error];
        } 
        else {
            [promise fulfillWithValue:result];
        }
    }];
    return promise;
}

ここで、元の非同期メソッドがプロミスの「リゾルバー」になります。promise は、タスクの最終的な結果または失敗の理由を指定して、実行 (成功) または拒否 (失敗) することができます。promise は、非同期操作またはメソッドの最終的な結果を保持します。

ラッパーは非同期メソッドであり、「保留中」状態の promise をすぐに返すことに注意してください。

最後に、メソッドまたはプロパティに成功ハンドラーと失敗ハンドラーを「登録」することにより、最終的な結果を取得します。then周りのいくつかの promise ライブラリはわずかに異なりますが、基本的には次のようになります。

`promise.then( <success-handler>, <error-handler> )`

Promise/A+ 仕様には最小限の API があります。上記は、基本的に Promise/A+ 仕様を実装するための 1 つの必要条件です。多くの単純なユース ケースでは、多くの場合、これで十分です。

ただし、場合によってはもう少し必要な場合もあります。たとえば、一連の非同期メソッドを「待機」し、すべてが完了したときに何かを実行する必要がある OP の問題などです。

幸いなことに、Promise は、より洗練されたヘルパー メソッドを非常に簡単に構築するための理想的な基本構成要素です。

多くの Promise ライブラリはユーティリティ メソッドを提供します。たとえば、allPromise を返し、Promise の配列を入力として受け取る非同期メソッドであるメソッド (または類似のメソッド) です。返された promise は、すべての操作が完了するか、いずれかが失敗したときに解決されます。次のようになります。

最初に promise の配列を作成し、同時にすべての非同期タスクを並行して開始します。

NSArray* tasks = @[
    [self downloadAppInfo],
    [self getAvailableHosts],
    [self getAvailableServices],
    [self getAvailableActions],
];

注: ここでは、タスクは既に実行されています (完了する可能性があります)。

ここで、上記のことを正確に実行するヘルパー メソッドを使用します。

RXPromise* finalPromise = [RXPromise all:tasks];

最終結果を取得します。

finalPromise.then(^id( results){
    [self doSomethingWithAppInfo:results[0] 
                  availableHosts:results[1] 
               availableServices:results[2]  
                availableActions:results[3]];
    return nil;
},  ^id(NSError* error) {
    NSLog(@"Error %@", error); // some async task failed - log the error
});

返された promise がメソッドで何らかの方法で解決されると、成功または失敗のハンドラーが呼び出されることに注意してくださいall:

返された promise ( finalPromise ) は、次の場合に解決されます。

  1. すべてのタスクが成功したとき、または
  2. 1 つのタスクが失敗しました

ケース 1) の場合、最終的な promise は、対応する各非同期タスクの結果を含む配列で解決されます。

ケース 2) の場合、最終的な promise は失敗した非同期タスクのエラーで解決されます。

(注: 利用可能ないくつかのライブラリはここで異なる場合があります)

RXPromise ライブラリには、いくつかの追加機能があります。

プロミスの非巡回グラフでキャンセル信号を転送する洗練されたキャンセル。

ハンドラーが実行されるディスパッチ キューを指定する方法。キューは、共有リソースへのアクセスを同期するために使用できます。

self.usersPromise = [self fetchUsers];

self.usersPromise.thenOn(dispatch_get_main_queue(), ^id(id users) {
    self.users = users;
    [self.tableView reloadData];
}, nil);

他のアプローチと比較すると、このdispatch_groupソリューションはスレッドをブロックするという問題があります。これは完全に「非同期」ではありません。また、キャンセルを実装することは、不可能ではないにしても非常に複雑です。

解決策はNSOperation複雑な祝福のようです。NSOperationsすでに があり、依存関係を定義するときに考慮する必要がある完了ハンドラーがない場合にのみ、エレガントになる可能性があります。そうでない場合は、雑然として複雑になります。

これまでに言及されていない別のソリューションは、Reactive Cocoaです。私見、それは事実上あらゆる複雑さの非同期問題を解決できる素晴らしいライブラリです。ただし、学習曲線が非常に急であり、アプリに多くのコードを追加する可能性があります。そして、つまずいた非同期問題の 90% は、キャンセル可能な promise で解決できると思います。さらに複雑な問題がある場合は、RAC を検討してください。

于 2013-09-24T20:51:15.857 に答える
1

ブロックベースのソリューションを作成したい場合は、次のようなことができます

- (void)syncEverything:(void(^)())success failure:(void(^)(NSError *error))failure
{
    __block int numBlocks = 4;
    __block BOOL alreadyFailed = NO;

    void (^subSuccess)(void) = ^(){
        numBlocks-=1;
        if ( numBlocks==0 ) {
            success();
        }
    };
    void (^subFailure)(NSError*) = ^(NSError* error){
        if ( !alreadyFailed ) {
            alreadyFailed = YES;
            failure(error);
        }
    };

    [self downloadAppInfo:subSuccess failure:subFailure];
    [self getAvailableHosts:subSuccess failure:subFailure];
    [self getAvailableServices:subSuccess failure:subFailure];
    [self getAvailableActions:subSuccess failure:subFailure];
}

それは一種の迅速で汚いものであり、ブロックコピーを行う必要があるかもしれません. 複数のメソッドが失敗した場合、全体的な失敗は 1 つだけです。

于 2013-09-24T15:55:54.993 に答える
0

これがdispatch_groupなしの私の解決策です。

+(void)doStuffWithCompletion:(void (^)(void))completion{
    __block NSInteger stuffRemaining = 3;

    void (^dataCompletionBlock)(void) = ^void(void) {
        stuffRemaining--;

        if (!stuffRemaining) {
            completion();
        }
    };

    for (NSInteger i = stuffRemaining-1; i > 0; i--) {
        [self doOtherStuffWithParams:nil completion:^() {
            dataCompletionBlock();
        }];
    }
}
于 2016-08-31T14:56:19.443 に答える