метод возвращается перед обработчиком завершения

Я разрабатываю networkUtil для своего проекта, мне нужен метод, который получает URL-адрес и возвращает JSON, полученный с этого URL-адреса, с помощью NSURLSessionDataTask для получения JSON с сервера. метод следующий:

+ (NSDictionary*) getJsonDataFromURL:(NSString *)urlString{
    __block NSDictionary* jsonResponse;
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        NSLog(@"%@", jsonResponse);
    }];

    [dataTask resume];

    return jsonResponse;
}

Проблема в том, что completionHandler внутри моего метода и сам метод выполняются в разных потоках, а в последней строке jsonResponse всегда >ноль

Как установить jsonResponse с возвращенным json из urlString?
Какова рекомендация для решения этой проблемы?


person Mehdi Ijadnazar    schedule 02.07.2015    source источник
comment
Это потому, что вы делаете асинхронные вызовы. Есть много вопросов по этому поводу.   -  person Larme    schedule 02.07.2015


Ответы (4)


Блок, который выполняется в NSURLSession, выполняется в другом потоке — ваш метод не ждет завершения блока.

У вас есть два варианта здесь

Первый. Отправить NS-уведомление

+ (void) getJsonDataFromURL:(NSString *)urlString{
       NSURLSession *session = [NSURLSession sharedSession];
       NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
           NSDictionary* jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
           NSLog(@"%@", jsonResponse);

           [[NSNotificationCenter defaultCenter] postNotificationName:@"JSONResponse" object:nil userInfo:@{@"response" : jsonResponse}];
       }];

       [dataTask resume];
}

Второй. Прошлый блок завершения для этого служебного метода

+ (void) getJsonDataFromURL:(NSString *)urlString
            completionBlock:(void(^)(NSDictionary* response))completion {
       NSURLSession *session = [NSURLSession sharedSession];
       NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
           NSDictionary* jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
           NSLog(@"%@", jsonResponse);

           completion(jsonResponse);
       }];

       [dataTask resume];
}
person Sergii Martynenko Jr    schedule 02.07.2015
comment
Что делает эта строка? [[NSNotificationCenter defaultCenter] postNotificationName:@JSONResponse object:nil userInfo:@{@response : jsonResponse}]; Проблема в том, что я хочу вернуть jsonResponse, один вариант - ваш второй вариант, я хочу знать, есть ли способ вернуть jsonResponse. - person Mehdi Ijadnazar; 02.07.2015
comment
Эта строка отправляет уведомление в NSNotificationCenter. . Почему вы хотите return это? Используйте второй подход для выполнения любых необходимых действий при получении ответа. Вы спрашивали о рекомендациях — я вам ответил - person Sergii Martynenko Jr; 02.07.2015
comment
проблема в том, что в другом методе я вызываю getJsonDataFromURL и устанавливаю возвращаемое значение в переменную и выполняю над ней некоторые процессы. + (NSNumber*) getServerVersion{ NSDictionary* versionUpdateJson = [NetworkUtil getJsonDataFromURL:updateServerURL]; if(versionUpdateJson){ NSNumber* updateVersion = [NSNumber numberWithFloat:[[versionUpdateJson valueForKey:@"serverVersion"] floatValue]]; return updateVersion; }else{ return @-1; } } опять возникает эта проблема - person Mehdi Ijadnazar; 02.07.2015
comment
Здесь опять те же подходы, предложил я. Та же ситуация - то же решение. Но еще лучше, я думаю, было бы избавиться от этого метода getServerVersion и вызвать getJsonDataFromURL напрямую. В случае, если это подходит для вашей организации приложений - person Sergii Martynenko Jr; 02.07.2015
comment
Так правда ли, что при использовании завершенияHandlers мы не можем вернуть значения, появившиеся внутри блока, и что подход к использованию этих значений должен быть вторым? - person Mehdi Ijadnazar; 02.07.2015
comment
Да, это правда. Правда, если блоки, в которых вы появляетесь, это значение выполняется асинхронно с вашим методом - person Sergii Martynenko Jr; 02.07.2015
comment
Нет причин объявлять jsonResponse вне блока. - person newacct; 03.07.2015
comment
@newacct Абсолютно. Отредактировано - person Sergii Martynenko Jr; 03.07.2015

Некоторые люди могут сказать, что это ужасный совет, но вы также можете синхронно загружать свои данные. Это должно быть сделано в фоновой очереди. Это не рекомендуемая практика, но в некоторых случаях (например, утилита командной строки, некритическая фоновая очередь) это нормально.

NSURLSession не имеет метода синхронной загрузки, но вы можете легко обойти его с помощью семафора:

+ (NSDictionary*) getJsonDataFromURL:(NSString *)urlString{
    __block NSDictionary* jsonResponse;

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); // Line 1

    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        NSLog(@"%@", jsonResponse);

        dispatch_semaphore_signal(semaphore); // Line 2
    }];

    [dataTask resume];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // Line 3

    return jsonResponse;
}

NSURLSession имеет delegateQueue, которое используется для «делегирования вызовов методов и обработчиков завершения, связанных с сеансом». По умолчанию NSURLSession всегда создает новую DelegateQueue во время инициализации. Но если вы сами устанавливаете очередь делегирования NSURLSession, убедитесь, что вы не вызываете свой метод в той же очереди, поскольку он заблокирует его.

person Avt    schedule 02.07.2015
comment
Да, действительно ужасный совет. Совсем не метод Objective-C. - person Sergii Martynenko Jr; 02.07.2015
comment
@SergiiMartynenkoJr для таких парней, как вы, я добавил пояснение. Кроме того, для конкретных случаев, таких как утилиты командной строки, это общий подход. - person Avt; 02.07.2015
comment
такой парень, как я, не может молча пройти мимо вашего ответа, не комментируя его =) - person Sergii Martynenko Jr; 02.07.2015
comment
Проблема с этим подходом заключается в том, что вам не гарантируется, в какую очередь будет отправлен блок dataTask. На самом деле его можно отправить в ту же последовательную очередь, в которой выполняется getJsonDataFromURL - person Sergii Martynenko Jr; 02.07.2015
comment
@SergiiMartynenkoJr Это не проблема. NSURLSession имеет свойство delegateQueue (developer.apple.com/library/ios/documentation/Foundation/), который используется для вызовов методов делегирования и обработчиков завершения, связанных с сеансом. По умолчанию NSURLSession ВСЕГДА создает новый delegateQueue во время инициализации. Если вы сами устанавливаете очередь делегирования, то, конечно, вы не должны ее блокировать. - person Avt; 02.07.2015
comment
да, я пропустил это. На самом деле, я должен был поститься, чтобы судить, позор мне. Мне жаль. Не могли бы вы отредактировать свой ответ, чтобы я мог проголосовать за него? Так что не позволяйте мне делать это без редактирования. Добавление слова или знаков препинания подойдет :) - person Sergii Martynenko Jr; 02.07.2015
comment
@SergiiMartynenkoJr Спасибо. - person Avt; 02.07.2015

Очевидно, что метод вернется до того, как блок будет завершен. Потому что это основная цель блока.

Вам нужно изменить что-то вроде этого:

NSDictionary* jsonResponse;

+ (NSDictionary*) getJsonDataFromURL:(NSString *)urlString{

    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        self.jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        NSLog(@"%@", jsonResponse);

        [dataTask resume];

// ad observer here that call method to update your UI
    }];


}
person Chetan Prajapati    schedule 02.07.2015

Это предполагаемое поведение «асинхронных вызовов». Они не должны блокировать вызывающий поток, а выполнять переданный блок, когда вызов выполнен.

Просто добавьте код, который должен быть выполнен после получения результата в блоке.

Итак, вместо…

+ (NSDictionary*) getJsonDataFromURL:(NSString *)urlString
{
  __block NSDictionary* jsonResponse;
  NSURLSession *session = [NSURLSession sharedSession];
  NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:
  ^(NSData *data, NSURLResponse *response, NSError *error) 
  {
    jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
    NSLog(@"%@", jsonResponse);
  }];

  [dataTask resume];

  return jsonResponse;
}
…
NSDictionary* jsonResponde = [self getJsonDataFromURL:url]; // BTW: get infringes the naming rules of Objective-C
// The code that has to be executed is here

… ты делаешь это:

+ (void) getJsonDataFromURL:(NSString *)urlString
{
  NSURLSession *session = [NSURLSession sharedSession];
  NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:
  ^(NSData *data, NSURLResponse *response, NSError *error) 
  {
    NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
    NSLog(@"%@", jsonResponse);
    // Place the code to be executed here <-----
  }];

  [dataTask resume];
}
…
[self getJsonDataFromURL:url]; // BTW: get infringes the naming rules of Objective-C
// No code here any more

Если код, выполняемый -getJsonDataFromURL:, зависит от вызывающей стороны, просто передайте его в качестве аргумента методу и выполните его в указанном месте. Если вам нужна помощь для этого, дайте мне знать. Я добавлю код для него.

Другим решением является использование семафора и ожидание выполнения обработчика завершения. Но это заблокирует пользовательский интерфейс и является непреднамеренным способом сделать это.

person Amin Negm-Awad    schedule 02.07.2015
comment
Вы отправляете сообщение класса объекту self - ошибка. И то, что вы предлагаете, является нарушением разделения ответственности. Что вы будете делать, если вам нужен этот метод в разных классах вашей программы, и каждый из них делает что-то свое при получении ответа? В - person Sergii Martynenko Jr; 02.07.2015
comment
1. self не обязательно является ссылкой на экземпляр объекта. Нет ошибок. 2. В этом случае я бы перечитал свой ответ, особенно: если код, выполняемый -getJsonDataFromURL:, зависит от вызывающей стороны, просто передайте его в качестве аргумента методу и выполните его в указанном месте. Если вам нужна помощь для этого, дайте мне знать. Я добавлю код для него. - person Amin Negm-Awad; 02.07.2015
comment
Итак, вы думаете, что этот метод должен вызываться только объектом класса? Это ошибка по смыслу контекста этого вопроса. А по смыслу вашего ответа - передача кода на выполнение self, то есть Class-object? Зачем? Это всегда будет то же самое. - person Sergii Martynenko Jr; 02.07.2015
comment
на самом деле я хочу разделить задачи и чтобы этот метод просто получал ответ json, чтобы я мог использовать этот метод позже, поэтому я не могу поместить код, который необходимо выполнить после этого метода, внутри блока. - person Mehdi Ijadnazar; 02.07.2015
comment
@SergiiMartynenkoJr Не знаю, зачем он это делает. Метод был методом класса в его Q. А в вашем A – упс. Это был не его вопрос, правильно ли это и нельзя сказать, потому что у нас недостаточно информации, чтобы ответить на этот незаданный вопрос. Пожалуйста, перечитайте вопрос. Но, конечно, там десятки примеров отправки сообщения self, указывающего на объект класса. Я сказал десятки? Я хотел сказать тонны. - person Amin Negm-Awad; 02.07.2015
comment
@Mahdi Так что просто передайте код для выполнения в качестве аргумента типа блока и выполните его внутри обработчика завершения, как сказано. - person Amin Negm-Awad; 02.07.2015
comment
Тогда нет причин объявлять jsonResponse вне блока. - person newacct; 03.07.2015
comment
@newacct Верно, взято из вопроса. Спасибо! - person Amin Negm-Awad; 03.07.2015