另一种解决方案是使用第三方库中提供的 Promise。我是 RXPromise 的作者,该库实现了 Promises/A+ 规范。但至少还有其他两个 Objective-C 实现。
Promise 表示异步方法或操作的最终结果:
-(Promise*) doSomethingAsync;
承诺是完成处理程序的完全替代品。此外,由于其明确的规范和基础设计,它具有一些非常有用的功能,使其特别容易处理相当复杂的异步问题。
首先,您需要将带有完成处理程序的异步方法包装成返回承诺的异步方法:
(有意地,您的方法返回“最终结果”和“潜在错误”,以更方便的完成处理程序为目的)
例如:
- (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 将持有异步操作或方法的最终结果。
请注意,包装器是一个异步方法,它立即返回处于“挂起”状态的 Promise。
最后,您可以通过使用
then
方法或属性注册成功和失败处理程序来获取最终结果。虽然一些 Promise 库略有不同,但基本上如下所示:
`promise.then( <success-handler>, <error-handler> )`
Promise/A+规范具有极简API。以上基本上就是实现Promise/A+规范所需的全部内容,通常在许多简单应用情况下已经足够了。
但有时您需要更多 - 例如OP的问题,需要“等待”一组异步方法完成,然后执行某些操作。
幸运的是,Promise是构建更复杂的辅助方法的理想基础模块,这可以相当容易地实现。
许多Promise库提供实用程序方法。因此,例如一个名为
all
的方法(或类似方法),它是一个异步方法,返回一个Promise并以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);
});
请注意,在
all:
方法中,无论返回的承诺以何种方式得到解决,都会调用成功或失败处理程序之一。
当以下情况发生时,返回的承诺(
finalPromise)将得到解决:
- 所有任务都成功完成。
- 一个任务失败。
对于情况1),最终承诺将通过一个包含每个相应异步任务结果的数组得到解决。
在情况2)下,最终承诺将通过失败的异步任务的错误得到解决。
(注:少数可用库可能存在差异)
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。