njc
@njc
Разработчик Java

Как реализовать callback для обновления UI из http-запроса (NSURLSession)?

Здравствуйте!

Пишу свое первое приложение на objective-c для iOS 7.1. Я, видимо, что-то принципиально не понимаю, поэтому у меня этот вопрос и возник.

Приложение довольно простое: клиент для взаимодействия с внешним сервисом по некому web api. Для отправки http-запросов я использую NSURLSession.
Архитектура у меня простая.
9a486489cd8f412db223c62a5bbdb51b.png
THETALETestViewController - это view controller. Данный класс использует THETALEAPI.h
THETALEAPI - это реализация web api. Я выделил в отдельный класс, чтобы каждый метод внешнего API был реализован в одном месте. Здесь происходит формирование запроса для каждого метода. Данный класс использует THETALEHttpHandler.h.
THETALEHttpHandler - этот класс отвечает непосредственно за отправку http-запроса и прием http-ответа.

Приведу код из THETALEHttpHandler.m для отправки POST - запроса.
-(void) sendPostToURL:(NSURL *)url withParams: (NSString *) inParams competion:(void (^) (NSData *data)) completion{
    NSLog(@"sendPostToURL");
    
    // _csrftoken некий токен в cookie
    NSMutableDictionary *cookieProperties = [NSMutableDictionary dictionary];
    [cookieProperties setObject:@"csrftoken" forKey:NSHTTPCookieName];
    [cookieProperties setObject:_csrftoken forKey:NSHTTPCookieValue];
    
    [cookieProperties setObject:[[NSDate date] dateByAddingTimeInterval:2629743] forKey:NSHTTPCookieExpires];
    
    NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:cookieProperties];
    [_httpCookieStorage setCookie:cookie];
    
    [_sessionConfig setHTTPCookieStorage:_httpCookieStorage];


 // пробовал и так
 // //   NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil //delegateQueue: [NSOperationQueue mainQueue]];   
    NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:_sessionConfig];
    
    
    NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:url];
    
    // _csrftoken некий токен в параметр
    NSString *params = [@[inParams, _csrftoken] componentsJoinedByString:@""];
    
    [urlRequest setHTTPMethod:@"POST"];
    [urlRequest setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];
   
    NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest:urlRequest
                                                       completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                        NSLog(@"Got response %@ with error %@.\n", response, error);
                                                           if(error == nil)
                                                           {
                                                               NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
                                                               NSLog(@"Data = %@",text);
                                                           }
                                                       completion(data);
                                                       }];
    [dataTask resume];
    
}


Так вот, блок completion выполняется не в main thread, а асинхронно, поэтому пока отправляется запрос и идет прием и обработка ответа - main thread выполняется дальше. В принципе, это понятно и логично, но я хочу "логиниться" с помощью данного метода, соответственно, по нажатию на кнопку "Войти" у меня должен отправиться запрос и после успешного ответа и его обработки пользователь должен увидеть ответ. Какими возможностями iOS добиться такого поведения, чтобы main thread ждал завершения выполнения асинхронного блока и выводил соответствующую информацию? Или, может быть, ему не стоить ждать, а просто его как-то нужно оповестить?

Если, конечно, не выносить отдельные классы, а все делать в viewController, то, предполагаю, что подошли бы делегаты, но я хочу внести немного модульности в свое приложение.

Думаю, что данное требование достаточно банально и ответ где-то рядом, прошу объяснить, заранее спасибо!
  • Вопрос задан
  • 3440 просмотров
Решения вопроса 1
kaspartus
@kaspartus
Блок completion нигде не выполняется и никуда не идет, он начинает выполняться, когда запрос выполнился так или иначе. А main thread никого ждать не должен. Просто в нужное время сессия вызовет completion-блок, действительно, не в главном потоке.

Немного советов и схема. Возможно предложу не идеальный вариант.
1. Выпиливаем все до создания сессии в отдельный метод, оно и логично - мы все подготавливаем к дальнейшей работе.
2. Пишем отдельно метод, который создает таск, при помощи которого будем логиниться(то что в вашем методе после создания сессии). Создаем сам таск при помощи метода dataTaskWithRequest:completionHandler:, completion-блок вызовется когда таск выполнится тем или иным образом. Если успех - к п.3
3. Вызываем отдельный метод внутри этого блока, где будем писать дальнейшую логику после логина, туда можно накидать создание других тасков. Метод лучше вызвать в главном потоке, т.к. наверняка захочется что-то сделать с UI.
[self performSelectorOnMainThread:@selector(afterLoginLogicMethod) withObject:nil waitUntilDone:NO];


P.S. По поводу callback'a от тасков
Можно его получать через completion-блоки, а можно через делегатные методы, кому как удобнее.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы