我有以下(伪)代码:
- (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也在主线程上运行.
Tri*_*ops 19
如果在主线程上也调用了完成块,则可能很难实现,因为在完成块可以执行之前,您的方法需要返回.您应该将异步方法的实现更改为:
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)