20

次の(疑似)コードがあります。

- (void)testAbc
{
    [someThing retrieve:@"foo" completion:^
    {
        NSArray* names = @[@"John", @"Mary", @"Peter", @"Madalena"];
        for (NSString name in names)
        {
            [someObject lookupName:name completion:^(NSString* urlString)
            {
                // A. Something that takes a few seconds to complete.
            }];

            // B. Need to wait here until A is completed.
        }
    }];

    // C. Need to wait here until all iterations above have finished.
    STAssertTrue(...);
}

このコードはメインスレッドで実行されており、完了ブロック A もメインスレッドにあります。

  • A が完了するのを B で待つにはどうすればよいですか?
  • その後、外側の完了ブロックが完了するのを C でどのように待ちますか?
4

7 に答える 7

3

私は、Mike Ash (http://www.mikeash.com/pyblog/friday-qa-2013-08-16-lets-build-dispatch-groups.html)が「完了時に複数のスレッドを待機し、次に、すべてのスレッドが終了したら何かを行います'. 良いことは、ディスパッチ グループを使用して、同期または非同期のいずれかで待機できることです。

Mike Ash のブログからコピーして変更した短い例:

    dispatch_group_t group = dispatch_group_create();

    for(int i = 0; i < 100; i++)
    {
        dispatch_group_enter(group);
        DoAsyncWorkWithCompletionBlock(^{
            // Async work has been completed, this must be executed on a different thread than the main thread

            dispatch_group_leave(group);
        });
    }

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

または、dispatch_group_wait の代わりに、すべてのブロックが完了したときに、非同期的に待機してアクションを実行することもできます。

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    UpdateUI();
});
于 2013-10-29T07:45:14.540 に答える
2

私は現在、多くの複雑な非同期パターンを非常に簡単に実装できるようにするライブラリ (ソースが GitHub にある RXPromise) を開発しています。

次のアプローチでは、クラスRXPromiseを利用し、100% 非同期のコードを生成します。つまり、ブロックはまったくありません。「待機」は、非同期タスクが終了またはキャンセルされたときに呼び出されるハンドラーによって実現されます。

また、ライブラリの一部ではないカテゴリも使用しますがNSArray、RXPromise ライブラリを使用して簡単に実装できます。

たとえば、コードは次のようになります。

- (RXPromise*)asyncTestAbc
{
    return [someThing retrieve:@"foo"]
    .then(^id(id unused /*names?*/) {
        // retrieve:@"foo" finished with success, now execute this on private queue:
        NSArray* names = @[@"John", @"Mary", @"Peter", @"Madalena"];
        return [names rx_serialForEach:^RXPromise* (id name) { /* return eventual result when array finished */
            return [someObject lookupName:name] /* return eventual result of lookup's completion handler */
            .thenOn(mainQueue, ^id(id result) {
                assert(<we are on main thread>);
                // A. Do something after a lookupName:name completes a few seconds later
                return nil;
            }, nil /*might be implemented to detect a cancellation and "backward" it to the lookup task */);
        }]
    },nil);
}

最終結果をテストするには:

[self asyncTestAbc]
.thenOn(mainQueue, ^id(id result) {
    // C. all `[someObject lookupName:name]` and all the completion handlers for
    // lookupName,  and `[someThing retrieve:@"foo"]` have finished.
    assert(<we are on main thread>);
    STAssertTrue(...);
}, id(NSError* error) {
    assert(<we are on main thread>);
    STFail(@"ERROR: %@", error);
});

メソッドは、非同期asyncTestABCであることを除いて、説明したことを正確に実行します。テスト目的で、完了するまで待つことができます。

  [[self asyncTestAbc].thenOn(...) wait];

ただし、メインスレッドで待機しないでください。そうしないとasyncTestAbc、メインスレッドでも完了ハンドラーが呼び出されるため、デッドロックが発生します。


これが役立つと思われる場合は、より詳細な説明をリクエストしてください。


注: RXPromise ライブラリはまだ「作業中」です。複雑な非同期パターンを扱うすべての人に役立つかもしれません。上記のコードは、現在 GitHub でマスターにコミットされていない機能を使用しています:thenOnハンドラーが実行されるキューを指定できるプロパティ。現在then、ハンドラーが実行されるパラメーター キューを省略したプロパティのみがあります。特に指定しない限り、すべてのハンドラーは共有プライベート キューで実行されます。提案は大歓迎です!

于 2013-07-30T16:34:53.957 に答える
2
int i = 0;
//the below code goes instead of for loop
NSString *name = [names objectAtIndex:i];

[someObject lookupName:name completion:^(NSString* urlString)
{
    // A. Something that takes a few seconds to complete.
    // B.
    i+= 1;
    [self doSomethingWithObjectInArray:names atIndex:i];


}];




/* add this method to your class */
-(void)doSomethingWithObjectInArray:(NSArray*)names atIndex:(int)i {
    if (i == names.count) {
        // C.
    }
    else {
        NSString *nextName = [names objectAtIndex:i];
        [someObject lookupName:nextName completion:^(NSString* urlString)
        {
            // A. Something that takes a few seconds to complete.
            // B.
            [self doSomethingWithObjectInArray:names atIndex:i+1];
        }];
    }
}

ここにコードを入力しただけなので、一部のメソッド名のスペルが間違っている可能性があります。

于 2013-07-29T09:26:59.957 に答える
1

多くの場合、メイン スレッドをブロックするのは不適切な方法です。アプリが応答しなくなるだけなので、代わりにこのようなことをしてみませんか?

NSArray *names;
int namesIndex = 0;
- (void)setup {

    // Insert code for adding loading animation

    [UIView animateWithDuration:1 animations:^{
        self.view.alpha = self.view.alpha==1?0:1;
    } completion:^(BOOL finished) {
        names = @[@"John", @"Mary", @"Peter", @"Madalena"];
        [self alterNames];
    }];
}

- (void)alterNames {

    if (namesIndex>=names.count) {
        // Insert code for removing loading animation
        // C. Need to wait here until all iterations above have finished.
        return;
    }


    NSString *name = [names objectAtIndex:namesIndex];
    [UIView animateWithDuration:1 animations:^{
        self.view.alpha = self.view.alpha==1?0:1;
    } completion:^(BOOL finished) {
        name = @"saf";
        // A. Something that takes a few seconds to complete.
        // B. Need to wait here until A is completed.

        namesIndex++;
        [self alterNames];
    }];

}

[UIView animation...] を使用して、例を完全に機能させました。コピーして viewcontroller.m に貼り付け、[self setup] を呼び出すだけです。もちろん、それを自分のコードに置き換える必要があります。

または、必要に応じて:

NSArray *names;
int namesIndex = 0;
- (void)setup {

    // Code for adding loading animation

    [someThing retrieve:@"foo" completion:^ {
        names = @[@"John", @"Mary", @"Peter", @"Madalena"];
        [self alterNames];
    }];
}

- (void)alterNames {

    if (namesIndex>=names.count) {
        // Code for removing loading animation
        // C. Need to wait here until all iterations above have finished.
        return;
    }

    NSString *name = [names objectAtIndex:namesIndex];
    [someObject lookupName:name completion:^(NSString* urlString) {
        name = @"saf";
        // A. Something that takes a few seconds to complete.
        // B. Need to wait here until A is completed.

        namesIndex++;
        [self alterNames];
    }];

}

説明:

  1. [self setup] を呼び出してすべてを開始します。
  2. someThing が「foo」を取得すると、ブロックが呼び出されます。つまり、 someThing が「foo」を取得するまで待機します (メインスレッドはブロックされません)。
  3. ブロックが実行されると、alterNames が呼び出されます
  4. 「names」内のすべての項目がループされた場合、「ループ」が停止し、C が実行される可能性があります。
  5. それ以外の場合は、名前を検索し、それが完了したら、何かを実行します (A)。これはメイン スレッドで発生するため (他に何も言っていません)、そこでも B を実行できます。
  6. したがって、A と B が完了したら、3 に戻ります。

見る?

あなたのプロジェクトで頑張ってください!

于 2013-07-29T09:55:19.140 に答える