Элегантный шаблон для длительных операций в 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, который позволяет вам выполнять блокирующие вызовы, если вы этого хотите.

Мне интересно, если кто-нибудь там придумал более чистый подход. Я чувствую, что с этим рано или поздно сталкивается большинство приложений, и мне не нужно изобретать велосипед.

0 ответов

Другие вопросы по тегам