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

mea*_*ers 19 objective-c ios

我有以下(伪)代码:

- (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(...);
}
Run Code Online (Sandbox Code Playgroud)

此代码在主线程上运行,完成块A也在主线程上运行.

  • 我如何在B等待A完成?
  • 如何在C处等待外部完成块完成?

Tri*_*ops 19

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

  1. 同步.
    要么
  2. 使用其他线程/队列完成.然后你可以使用Dispatch Semaphores等待.使用value初始化信号量0,然后调用wait主线程并signal完成.

在任何情况下,阻止主线程在GUI应用程序中是一个非常糟糕的主意,但这不是你的问题的一部分.在测试,命令行工具或其他特殊情况下可能需要阻止主线程.在这种情况下,请进一步阅读:


如何在主线程等待主线程回调:

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

主线是特殊的.它运行+[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");
Run Code Online (Sandbox Code Playgroud)

这两个布尔值是为了确保块立即同步完成.

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

开始
等待......
完成!
继续

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

开始
完成!
继续

什么是魔术?调用CFRunLoopRun()不会立即返回,而是仅在CFRunLoopStop()调用时返回.此代码在主RunLoop因此运行主RunLoop 再次将恢复所有预定块,定时器,插座的执行等.

警告:可能的问题是,所有其他计划的计时器和块将在此期间执行.此外,如果从未调用完成块,则代码将永远不会到达Continue日志.

您可以将此逻辑包装在一个对象中,这样可以更容易地重复使用此模式:

@interface MYRunLoopSemaphore : NSObject

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

@end
Run Code Online (Sandbox Code Playgroud)

所以代码将简化为:

MYRunLoopSemaphore *semaphore = [MYRunLoopSemaphore new];
[self performOperationWithCompletionOnMainQueue:^{
    [semaphore signal];
}];
[semaphore wait];
Run Code Online (Sandbox Code Playgroud)

  • 等待完成的东西是完成块的用途.所以在它自己的完成块中调用lookupName以递归遍历每个数组元素应该可以正常工作. (3认同)