Элегантный шаблон для длительных операций в GCD и сообщения об ошибках пользователю?
Я пытаюсь очистить какой-то код, который у меня вышел из-под контроля. Часто возникают ситуации, когда мне нужно взаимодействовать с парой удаленных 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 с возвратами (та же идея, что и в старой школе с меткой очистки) - это один из способов. Другой способ - использовать объект ошибки в качестве переменной "следует продолжить" и сделать что-то вроде:
if (!error) {
doStuff(&error)
}
if (!error) {
[...]
Добавление GCD немного усложняет ситуацию, потому что теперь вы должны убедиться, что тяжелая работа всегда выполняется в фоновом режиме, а ошибки сообщаются только в основном потоке. Каждая библиотека API работает по-своему, поэтому у вас есть что-то вроде Facebook, который принимает блоки, которые будут выполняться только в главном потоке, который вы должны переплетать с Parse, который позволяет вам выполнять блокирующие вызовы, если вы этого хотите.
Мне интересно, если кто-нибудь там придумал более чистый подход. Я чувствую, что с этим рано или поздно сталкивается большинство приложений, и мне не нужно изобретать велосипед.