我正在寻找清理一些我失去控制的代码。通常情况下,我需要与几个远程 API 交互,例如 Parse 和 Facebook,也许是 Core Data,而用户正在等待,盯着我的活动指示器旋转。
我的要求是:
- 当然,一切缓慢都必须在后台线程中
- 没有静默或忽略的错误消息。
- 我想帮助用户(这样他就不会惊慌失措)和我自己(当我接到支持电话时,我需要找出问题所在)尽可能有用的错误消息。
- 我希望逻辑是可维护的。像下面这样的块在圈复杂度上的上升非常快,如果处理不当,尤其是在添加多线程的情况下,它会成为一场噩梦。
我现在使用的模式如下:
- (void)sampleFacebookProcessingCall
{
[self.spinner startAnimating];
[FBRequestConnection startForMeWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *facebookRequestError)
{
// we're back in main queue, let's return to a secondary queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// don't want do display the actual error to the user
NSString *errorMessage;
NSError *error;
@try {
if (facebookRequestError) {
errorMessage = @"Could not connect to Facebook, please try again later.";
return;
}
[Helper someParseCall:&error];
if (error) {
errorMessage = @"The operation could not be completed, please try again later. Code: FOO1";
return;
}
[Helper someOtherParseCall:&error];
if (error) {
errorMessage = @"The operation could not be completed, please try again later. Code: FOO2";
return;
}
[...]
}
@finally {
dispatch_async(dispatch_get_main_queue(), ^{
[self.activityIndicator stopAnimating];
if (errorMessage) {
// there might be half-baked state out there afer the error
[self cleanupResources];
[UIHelper displayErrorAlertViewWithMessage:errorMessage];
}
});
}
});
}];
}
现在,当发生错误时,肯定有不同的模式可以通过流转义。带有返回的@try/@finally 模式(与带有清理标签的老式 goto 相同)是一种方法。另一种方法是将错误对象用作“应该继续”变量并执行以下操作:
if (!error) {
doStuff(&error)
}
if (!error) {
[...]
GCD 的添加使事情变得有些复杂,因为现在您必须确保繁重的工作始终在后台完成,并且只在主线程中报告错误。每个 API 的库做的事情都有点不同,所以你有像 Facebook 这样的东西,它只接受要在主线程中执行的块,你必须与 Parse 交织在一起,如果你愿意,你可以运行阻塞调用。
我想知道是否有人提出了更清洁的方法。我觉得这是大多数应用程序迟早要处理的事情,我不需要重新发明轮子。