如何等待具有完成块(全部在主线程上)的方法?

时间:2021-12-31 21:00:30

I have the following (pseudo) code:

我有以下(伪)码:

- (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(...);
}

This code is running on main thread, and also the completion block A is on main thread.

这段代码在主线程上运行,并且完成块A也在主线程上。

  • How do I wait at B for A to complete?
  • 我如何在B等待A完成?
  • How do subsequently wait at C for the outer completion block to complete?
  • 后续如何在C等待外部完成块完成?

7 个解决方案

#1


17  

If your completion block is also called on the Main Thread, it might be difficult to achieve this, because before the completion block can execute, your method need to return. You should change implementation of the asynchronous method to:

如果在主线程上也调用了完成块,那么可能很难实现这个目标,因为在完成块执行之前,您的方法需要返回。您应该将异步方法的实现更改为:

  1. Be synchronous.
    or
  2. 是同步的。或
  3. Use other thread/queue for completion. Then you can use Dispatch Semaphores for waiting. You initialize a semaphore with value 0, then call wait on main thread and signal in completion.
  4. 使用其他线程/队列完成。然后您可以使用分派信号量等待。您用值0初始化一个信号量,然后调用主线程上的wait和完成时的信号。

In any case, blocking Main Thread is very bad idea in GUI applications, but that wasn't part of your question. Blocking Main Thread may be required in tests, in command-line tools, or other special cases. In that case, read further:

无论如何,阻塞主线程在GUI应用程序中是非常糟糕的想法,但这不是您的问题的一部分。在测试、命令行工具或其他特殊情况下,可能需要阻塞主线程。在这种情况下,请进一步阅读:


How to wait for Main Thread callback on the Main Thread:

There is a way to do it, but could have unexpected consequences. Proceed with caution!

有一种方法可以做到这一点,但可能会产生意想不到的后果。谨慎行事!

Main Thread is special. It runs +[NSRunLoop mainRunLoop] which handles also +[NSOperationQueue mainQueue] and dispatch_get_main_queue(). All operations or blocks dispatched to these queues will be executed within the Main Run Loop. This means, that the methods may take any approach to scheduling the completion block, this should work in all those cases. Here it is:

主线程是特别的。它运行+[NSRunLoop mainRunLoop],它还处理+[NSOperationQueue mainQueue]和dispatch_get_main_queue()。分配给这些队列的所有操作或块将在主运行循环中执行。这意味着,这些方法可以采用任何方法来调度完成块,这在所有这些情况下都应该有效。这里是:

__block BOOL isRunLoopNested = NO;
__block BOOL isOperationCompleted = NO;
NSLog(@"Start");
[self performOperationWithCompletionOnMainQueue:^{
    NSLog(@"Completed!");
    isOperationCompleted = YES;
    if (isRunLoopNested) {
        CFRunLoopStop(CFRunLoopGetCurrent()); // CFRunLoopRun() returns
    }
}];
if ( ! isOperationCompleted) {
    isRunLoopNested = YES;
    NSLog(@"Waiting...");
    CFRunLoopRun(); // Magic!
    isRunLoopNested = NO;
}
NSLog(@"Continue");

Those two booleans are to ensure consistency in case of the block finished synchronously immediately.

这两个布尔值是为了在块立即同步完成时确保一致性。

In case the -performOperationWithCompletionOnMainQueue: is asynchronous, the output would be:

如果-performOperationWithCompletionOnMainQueue:是异步的,输出将是:

Start
Waiting...
Completed!
Continue

开始等待……完成了!继续

In case the method is synchronous, the output would be:

如果方法是同步的,输出为:

Start
Completed!
Continue

开始完成!继续

What is the Magic? Calling CFRunLoopRun() doesn’t return immediately, but only when CFRunLoopStop() is called. This code is on Main RunLoop so running the Main RunLoop again will resume execution of all scheduled block, timers, sockets and so on.

魔法是什么?调用CFRunLoopRun()不会立即返回,但只会在调用CFRunLoopStop()时返回。此代码位于主运行循环上,因此再次运行主运行循环将恢复所有计划块、计时器、套接字等的执行。

Warning: The possible problem is, that all other scheduled timers and block will be executed in meantime. Also, if the completion block is never called, your code will never reach Continue log.

警告:可能的问题是,所有其他计划的计时器和阻塞将同时执行。而且,如果从来没有调用完成块,您的代码将永远不会到达Continue日志。

You could wrap this logic in an object, that would make easier to use this pattern repeatedy:

您可以将这个逻辑封装到一个对象中,这样可以更容易地使用这个模式repeatedy:

@interface MYRunLoopSemaphore : NSObject

- (BOOL)wait;
- (BOOL)signal;

@end

So the code would be simplified to this:

所以代码会被简化为:

MYRunLoopSemaphore *semaphore = [MYRunLoopSemaphore new];
[self performOperationWithCompletionOnMainQueue:^{
    [semaphore signal];
}];
[semaphore wait];

#2


4  

I think that Mike Ash (http://www.mikeash.com/pyblog/friday-qa-2013-08-16-lets-build-dispatch-groups.html has exactly the answer to 'waiting for several threads on completion and then do something when all threads are finished'. The nice thing is that you even can wait either synchronously or a-synchronously, using dispatch groups.

我认为Mike Ash (http://www.mikeash.com/pyblog/friday-qa-2013-08-16-lets-build- dispatchgroups.html确切地回答了“在完成时等待几个线程,然后在所有线程完成时做一些事情”。好处是,您甚至可以使用分派组以同步方式或a同步方式等待。

A short example copied and modified from Mike Ash his blog:

从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);

Alternatively, you can a-synchronously wait and perform an action when all blocks completed instead of the dispatch_group_wait:

或者,当所有块都完成时,您可以同步等待并执行操作,而不是dispatch_group_wait:

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    UpdateUI();
});

#3


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];
        }];
    }
}

I just typed the code here, so some methods names might be spelled wrong.

我在这里输入了代码,所以有些方法的名字可能拼错了。

#4


2  

I'm currently developing a library (RXPromise, whose sources are on GitHub) which makes a number of complex asynchronous patterns quite easy to implement.

我目前正在开发一个库(RXPromise,它的源代码在GitHub上),它使得许多复杂的异步模式非常容易实现。

The following approach utilizes a class RXPromise and yields code which is 100% asynchronous - which means, there is absolutely no blocking. "waiting" will be accomplished through the handlers which get called when an asynchronous tasks is finished or cancelled.

下面的方法利用类RXPromise并生成100%异步的代码——这意味着绝对没有阻塞。“等待”将通过在完成或取消异步任务时调用的处理程序完成。

It also utilizes a category for NSArray which is not part of the library - but can be easily implemented utilizing RXPromise library.

它还利用了NSArray的一个类别,这不是库的一部分,但是可以使用RXPromise库轻松实现。

For example, your code could then look like this:

例如,您的代码可以如下所示:

- (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);
}

In order to test the final result:

为了测试最终结果:

[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);
});

The method asyncTestABC will exactly do what you have described - except that it's asynchronous. For testing purposes you can wait until it completes:

asyncTestABC方法将执行您所描述的操作——除了它是异步的。为了测试目的,您可以等到它完成:

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

However, you must not wait on the main thread, otherwise you get a deadlock since asyncTestAbc invokes completion handler on the main thread, too.

但是,您不能等待主线程,否则会出现死锁,因为asyncTestAbc也会在主线程上调用补全处理程序。


Please request a more detailed explanation if you find this useful!

如果你觉得这个有用,请要求更详细的解释!


Note: the RXPromise library is still "work under progress". It may help everybody dealing with complex asynchronous patterns. The code above uses a feature not currently committed to master on GitHub: Property thenOn where a queue can be specified where handlers will be executed. Currently there is only property then which omits the parameter queue where handler shall run. Unless otherwise specified all handler run on a shared private queue. Suggestions are welcome!

注意:RXPromise库仍在“工作中”。它可以帮助每个人处理复杂的异步模式。上面的代码使用了一个在GitHub: Property thenOn上没有提交给master的特性,在那里可以指定一个队列,在那里处理程序将被执行。当前只有一个属性,它省略了处理程序应该运行的参数队列。除非另外指定所有处理程序都在共享的私有队列上运行。建议欢迎!

#5


1  

It's often a bad approach to block the main thread, it will just make your app unresponsive, so why not do something like this instead?

阻塞主线程通常是一种不好的方法,它只会使应用程序无法响应,所以为什么不做这样的事情呢?

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];
    }];

}

I have just used [UIView animation...] to make to example fully functional. Just copy and paste into your viewcontroller.m and call [self setup]; Of course, you should replace that with your code.

我刚刚用了[UIView动画…[范例为了使例子充分发挥作用。复制粘贴到你的视图控制器中。m调用[self setup];当然,您应该用代码替换它。

Or if you want:

或者如果你想:

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];
    }];

}

Explanation:

解释:

  1. Start everything by calling [self setup];
  2. 通过调用[self setup]启动一切;
  3. A block will be called when someThing retrieves "foo", in other words, it will wait until someThing retrieves "foo" (and the main thread won't be blocked)
  4. 当某些东西检索到“foo”时,将会调用一个块,换句话说,它将等待直到某个检索到“foo”(并且主线程不会被阻塞)
  5. When the block is executed, alterNames is called
  6. 执行块时,调用alterNames
  7. If all the items in "names" have been looped through, the "looping" will stop and C could be executed.
  8. 如果“names”中的所有项都被循环遍历,那么“循环”将停止,并可以执行C。
  9. Else, lookup the name, and when it's done, do something with it (A), and because it happens on the main thread (You haven't said anything else), you could do B there too.
  10. 另外,查找名称,当它完成时,用它(A)做一些事情,因为它发生在主线程上(您没有说其他任何东西),您也可以在那里执行B。
  11. So, when A and B is complete, jump back to 3
  12. 当A和B完成时,跳回到3

See?

看到了吗?

Good luck with your project!

祝你的项目好运!

#6


1  

Lots of good general-purpose answers above - but it looks like what you're trying to do is write a unit test for a method that uses a completion block. You don't know if the test has passed until the block is called, which happens asynchronously.

上面有很多很好的通用答案——但是看起来您要做的是为使用完成块的方法编写单元测试。在调用块之前,您不知道测试是否已经通过,这是异步发生的。

In my current project, I'm using SenTestingKitAsync to do this. It extends OCTest so that after all the tests are run, it executes whatever's waiting on the main run loop and evaluates those assertions as well. So your test could look like:

在我当前的项目中,我使用SenTestingKitAsync来实现这一点。它扩展了OCTest,以便在运行所有测试之后,它执行在主运行循环上等待的任何东西,并评估这些断言。所以你的测试应该是:

- (void)testAbc
{
    [someThing retrieve:@"foo" completion:^
    {
        STSuccess();
    }];

    STFailAfter(500, @"block should have been called");
}

I would also recommend testing someThing and someObject in two separate tests, but that's regardless of the asynchronous nature of what you're testing.

我还建议在两个单独的测试中测试某物和某物,但这与所测试的异步性无关。

#7


0  

 Move B and C to two methods.

int flagForC = 0, flagForB = 0;
     [someThing retrieve:@"foo" completion:^
    {
        flagForC++;
        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.
               flagForB++;

               if (flagForB == [names Count])
               {
                   flagForB = 0;
                   //call B
                    if (flagForC == thresholdCount)
                    {
                          flagForC = 0;
                         //Call C 
                    }
               }
            }];


        }
    }];

#1


17  

If your completion block is also called on the Main Thread, it might be difficult to achieve this, because before the completion block can execute, your method need to return. You should change implementation of the asynchronous method to:

如果在主线程上也调用了完成块,那么可能很难实现这个目标,因为在完成块执行之前,您的方法需要返回。您应该将异步方法的实现更改为:

  1. Be synchronous.
    or
  2. 是同步的。或
  3. Use other thread/queue for completion. Then you can use Dispatch Semaphores for waiting. You initialize a semaphore with value 0, then call wait on main thread and signal in completion.
  4. 使用其他线程/队列完成。然后您可以使用分派信号量等待。您用值0初始化一个信号量,然后调用主线程上的wait和完成时的信号。

In any case, blocking Main Thread is very bad idea in GUI applications, but that wasn't part of your question. Blocking Main Thread may be required in tests, in command-line tools, or other special cases. In that case, read further:

无论如何,阻塞主线程在GUI应用程序中是非常糟糕的想法,但这不是您的问题的一部分。在测试、命令行工具或其他特殊情况下,可能需要阻塞主线程。在这种情况下,请进一步阅读:


How to wait for Main Thread callback on the Main Thread:

There is a way to do it, but could have unexpected consequences. Proceed with caution!

有一种方法可以做到这一点,但可能会产生意想不到的后果。谨慎行事!

Main Thread is special. It runs +[NSRunLoop mainRunLoop] which handles also +[NSOperationQueue mainQueue] and dispatch_get_main_queue(). All operations or blocks dispatched to these queues will be executed within the Main Run Loop. This means, that the methods may take any approach to scheduling the completion block, this should work in all those cases. Here it is:

主线程是特别的。它运行+[NSRunLoop mainRunLoop],它还处理+[NSOperationQueue mainQueue]和dispatch_get_main_queue()。分配给这些队列的所有操作或块将在主运行循环中执行。这意味着,这些方法可以采用任何方法来调度完成块,这在所有这些情况下都应该有效。这里是:

__block BOOL isRunLoopNested = NO;
__block BOOL isOperationCompleted = NO;
NSLog(@"Start");
[self performOperationWithCompletionOnMainQueue:^{
    NSLog(@"Completed!");
    isOperationCompleted = YES;
    if (isRunLoopNested) {
        CFRunLoopStop(CFRunLoopGetCurrent()); // CFRunLoopRun() returns
    }
}];
if ( ! isOperationCompleted) {
    isRunLoopNested = YES;
    NSLog(@"Waiting...");
    CFRunLoopRun(); // Magic!
    isRunLoopNested = NO;
}
NSLog(@"Continue");

Those two booleans are to ensure consistency in case of the block finished synchronously immediately.

这两个布尔值是为了在块立即同步完成时确保一致性。

In case the -performOperationWithCompletionOnMainQueue: is asynchronous, the output would be:

如果-performOperationWithCompletionOnMainQueue:是异步的,输出将是:

Start
Waiting...
Completed!
Continue

开始等待……完成了!继续

In case the method is synchronous, the output would be:

如果方法是同步的,输出为:

Start
Completed!
Continue

开始完成!继续

What is the Magic? Calling CFRunLoopRun() doesn’t return immediately, but only when CFRunLoopStop() is called. This code is on Main RunLoop so running the Main RunLoop again will resume execution of all scheduled block, timers, sockets and so on.

魔法是什么?调用CFRunLoopRun()不会立即返回,但只会在调用CFRunLoopStop()时返回。此代码位于主运行循环上,因此再次运行主运行循环将恢复所有计划块、计时器、套接字等的执行。

Warning: The possible problem is, that all other scheduled timers and block will be executed in meantime. Also, if the completion block is never called, your code will never reach Continue log.

警告:可能的问题是,所有其他计划的计时器和阻塞将同时执行。而且,如果从来没有调用完成块,您的代码将永远不会到达Continue日志。

You could wrap this logic in an object, that would make easier to use this pattern repeatedy:

您可以将这个逻辑封装到一个对象中,这样可以更容易地使用这个模式repeatedy:

@interface MYRunLoopSemaphore : NSObject

- (BOOL)wait;
- (BOOL)signal;

@end

So the code would be simplified to this:

所以代码会被简化为:

MYRunLoopSemaphore *semaphore = [MYRunLoopSemaphore new];
[self performOperationWithCompletionOnMainQueue:^{
    [semaphore signal];
}];
[semaphore wait];

#2


4  

I think that Mike Ash (http://www.mikeash.com/pyblog/friday-qa-2013-08-16-lets-build-dispatch-groups.html has exactly the answer to 'waiting for several threads on completion and then do something when all threads are finished'. The nice thing is that you even can wait either synchronously or a-synchronously, using dispatch groups.

我认为Mike Ash (http://www.mikeash.com/pyblog/friday-qa-2013-08-16-lets-build- dispatchgroups.html确切地回答了“在完成时等待几个线程,然后在所有线程完成时做一些事情”。好处是,您甚至可以使用分派组以同步方式或a同步方式等待。

A short example copied and modified from Mike Ash his blog:

从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);

Alternatively, you can a-synchronously wait and perform an action when all blocks completed instead of the dispatch_group_wait:

或者,当所有块都完成时,您可以同步等待并执行操作,而不是dispatch_group_wait:

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    UpdateUI();
});

#3


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];
        }];
    }
}

I just typed the code here, so some methods names might be spelled wrong.

我在这里输入了代码,所以有些方法的名字可能拼错了。

#4


2  

I'm currently developing a library (RXPromise, whose sources are on GitHub) which makes a number of complex asynchronous patterns quite easy to implement.

我目前正在开发一个库(RXPromise,它的源代码在GitHub上),它使得许多复杂的异步模式非常容易实现。

The following approach utilizes a class RXPromise and yields code which is 100% asynchronous - which means, there is absolutely no blocking. "waiting" will be accomplished through the handlers which get called when an asynchronous tasks is finished or cancelled.

下面的方法利用类RXPromise并生成100%异步的代码——这意味着绝对没有阻塞。“等待”将通过在完成或取消异步任务时调用的处理程序完成。

It also utilizes a category for NSArray which is not part of the library - but can be easily implemented utilizing RXPromise library.

它还利用了NSArray的一个类别,这不是库的一部分,但是可以使用RXPromise库轻松实现。

For example, your code could then look like this:

例如,您的代码可以如下所示:

- (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);
}

In order to test the final result:

为了测试最终结果:

[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);
});

The method asyncTestABC will exactly do what you have described - except that it's asynchronous. For testing purposes you can wait until it completes:

asyncTestABC方法将执行您所描述的操作——除了它是异步的。为了测试目的,您可以等到它完成:

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

However, you must not wait on the main thread, otherwise you get a deadlock since asyncTestAbc invokes completion handler on the main thread, too.

但是,您不能等待主线程,否则会出现死锁,因为asyncTestAbc也会在主线程上调用补全处理程序。


Please request a more detailed explanation if you find this useful!

如果你觉得这个有用,请要求更详细的解释!


Note: the RXPromise library is still "work under progress". It may help everybody dealing with complex asynchronous patterns. The code above uses a feature not currently committed to master on GitHub: Property thenOn where a queue can be specified where handlers will be executed. Currently there is only property then which omits the parameter queue where handler shall run. Unless otherwise specified all handler run on a shared private queue. Suggestions are welcome!

注意:RXPromise库仍在“工作中”。它可以帮助每个人处理复杂的异步模式。上面的代码使用了一个在GitHub: Property thenOn上没有提交给master的特性,在那里可以指定一个队列,在那里处理程序将被执行。当前只有一个属性,它省略了处理程序应该运行的参数队列。除非另外指定所有处理程序都在共享的私有队列上运行。建议欢迎!

#5


1  

It's often a bad approach to block the main thread, it will just make your app unresponsive, so why not do something like this instead?

阻塞主线程通常是一种不好的方法,它只会使应用程序无法响应,所以为什么不做这样的事情呢?

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];
    }];

}

I have just used [UIView animation...] to make to example fully functional. Just copy and paste into your viewcontroller.m and call [self setup]; Of course, you should replace that with your code.

我刚刚用了[UIView动画…[范例为了使例子充分发挥作用。复制粘贴到你的视图控制器中。m调用[self setup];当然,您应该用代码替换它。

Or if you want:

或者如果你想:

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];
    }];

}

Explanation:

解释:

  1. Start everything by calling [self setup];
  2. 通过调用[self setup]启动一切;
  3. A block will be called when someThing retrieves "foo", in other words, it will wait until someThing retrieves "foo" (and the main thread won't be blocked)
  4. 当某些东西检索到“foo”时,将会调用一个块,换句话说,它将等待直到某个检索到“foo”(并且主线程不会被阻塞)
  5. When the block is executed, alterNames is called
  6. 执行块时,调用alterNames
  7. If all the items in "names" have been looped through, the "looping" will stop and C could be executed.
  8. 如果“names”中的所有项都被循环遍历,那么“循环”将停止,并可以执行C。
  9. Else, lookup the name, and when it's done, do something with it (A), and because it happens on the main thread (You haven't said anything else), you could do B there too.
  10. 另外,查找名称,当它完成时,用它(A)做一些事情,因为它发生在主线程上(您没有说其他任何东西),您也可以在那里执行B。
  11. So, when A and B is complete, jump back to 3
  12. 当A和B完成时,跳回到3

See?

看到了吗?

Good luck with your project!

祝你的项目好运!

#6


1  

Lots of good general-purpose answers above - but it looks like what you're trying to do is write a unit test for a method that uses a completion block. You don't know if the test has passed until the block is called, which happens asynchronously.

上面有很多很好的通用答案——但是看起来您要做的是为使用完成块的方法编写单元测试。在调用块之前,您不知道测试是否已经通过,这是异步发生的。

In my current project, I'm using SenTestingKitAsync to do this. It extends OCTest so that after all the tests are run, it executes whatever's waiting on the main run loop and evaluates those assertions as well. So your test could look like:

在我当前的项目中,我使用SenTestingKitAsync来实现这一点。它扩展了OCTest,以便在运行所有测试之后,它执行在主运行循环上等待的任何东西,并评估这些断言。所以你的测试应该是:

- (void)testAbc
{
    [someThing retrieve:@"foo" completion:^
    {
        STSuccess();
    }];

    STFailAfter(500, @"block should have been called");
}

I would also recommend testing someThing and someObject in two separate tests, but that's regardless of the asynchronous nature of what you're testing.

我还建议在两个单独的测试中测试某物和某物,但这与所测试的异步性无关。

#7


0  

 Move B and C to two methods.

int flagForC = 0, flagForB = 0;
     [someThing retrieve:@"foo" completion:^
    {
        flagForC++;
        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.
               flagForB++;

               if (flagForB == [names Count])
               {
                   flagForB = 0;
                   //call B
                    if (flagForC == thresholdCount)
                    {
                          flagForC = 0;
                         //Call C 
                    }
               }
            }];


        }
    }];