2014-02-12 3 views
5

У меня есть следующая проблема. У меня есть модель, называемая пользователем. Когда пользователь теперь подключается к Facebook, мое приложение проверяет, существует ли пользователь в базе данных. Чтобы не замораживать пользовательский интерфейс (поскольку я прихожу с Android), я думал использовать NSURLConnection sendAsynchronousRequest. Сначала работала следующая:Дождитесь завершения NSURLConnection sendAsynchronousRequest

В моей модели пользователя был метод, выполняющий всю задачу AsynchronousRequest, а затем, когда законченный, установил переменную для загрузки. Тогда другие классы могли бы просто проверить с while (!user.loading), если Запрос был закончен или нет. Проблема, которая пришла сюда ко мне, заключалась в том, что теперь мне пришлось поместить этот метод в каждую модель. Поэтому вместо этого я создал новый класс HTTPPost. Этот класс теперь имеет метод, который получает NSDictionary и возвращает его. Это работает ПОЧТИ. Проблема, с которой я столкнулся сейчас, заключается в том, что я не мог определить, был ли процесс завершен или нет. Поэтому я начал создавать новый класс под названием Globals и использовать глобальную переменную loading. Но глобальная переменная ALWAYS NO. Итак, что было бы лучшим способом сделать это?

Вот мой код:

Это где я проверяю для пользователя и загрузить его. resultDictionary является NSDictionary, где все загружается в, но всегда nil

[user loadModelFrom:[NSString stringWithFormat:@"WHERE facebookId='%@'", graphUser.id]]; 
     NSLog(@"%@", user.resultDictionary); 
     if (user.resultDictionary == nil) { 
      NSLog(@"NIL"); 
     } else { 
      NSLog(@"NOT NIL"); 
     } 

Проблема сейчас в том, что, так как я послав AsynchronousRequest, то resultDictionary всегда равна нулю. То, что я делал раньше, и работало, было следующим.

В моей модели у меня был HTTP-запрос и переменная с именем loading. Теперь я ставлю нагрузку на ложь, пока ответа не было сделано в NSDictionary

returnDict = [NSJSONSerialization JSONObjectWithData: [responseBody dataUsingEncoding:NSUTF8StringEncoding] 
                    options: NSJSONReadingMutableContainers 
                    error: &error]; 

Но тогда у меня была еще одна проблема. Я должен был сделать это во всех моих моделях снова ... Итак, я создал новый класс, который подклассы NSObject, который имеет асинхронныйRequest. Это целом запрос на

-(NSDictionary *)doHttpRequest:(NSDictionary *)postDict{ 
    loading = NO; 
    __block NSDictionary *returnDict; 
    NSError *error; 
    NSString *jsonString; 
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:postDict 
                 options:NSJSONWritingPrettyPrinted // Pass 0 if you don't care about the readability of the generated string 
                 error:&error]; 

    if (! jsonData) { 
     NSLog(@"Got an error: %@", error); 
    } else { 
     jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 
    } 

    NSURL *aUrl = [NSURL URLWithString:@"http://xx.xx-xx.xx/xx.xx"]; 
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:aUrl 
                  cachePolicy:NSURLRequestUseProtocolCachePolicy 
                 timeoutInterval:60.0]; 
    NSOperationQueue *queue = [[NSOperationQueue alloc] init]; 
    NSString *authStr = [NSString stringWithFormat:@"%@:%@", @"xx", @"xx"]; 
    NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding]; 
    NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodedString]]; 
    [request setValue:authValue forHTTPHeaderField:@"Authorization"]; 
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-type"]; 
    [request setHTTPMethod:@"POST"]; 
    [request setHTTPBody:[jsonString dataUsingEncoding:NSUTF8StringEncoding]]; 

    [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) 
    { 
     NSString *responseBody = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 
     returnDict    = [NSJSONSerialization JSONObjectWithData: [responseBody dataUsingEncoding:NSUTF8StringEncoding] 
                    options: NSJSONReadingMutableContainers 
                    error: &error]; 

    }]; 
    [queue waitUntilAllOperationsAreFinished]; 
    loading = YES; 
    return returnDict; 
} 

Как вы можете видеть, что я есть сейчас переменную loading. Это глобальная переменная. Но почему-то переменная всегда НЕТ.

Что было бы лучшим способом сделать это? Надеюсь, я понятен, я новичок в Objective-C, а английский - не мой родной язык.

UPDATE

Я изменил код, чтобы посмотреть, как пользователь представленная здесь, но до сих пор не работает!

HTTPPost.h 
-(void)doHttpRequest:(NSDictionary *)postDict completion:(void(^)(NSDictionary *dict, NSError *error))completion { 
    __block NSDictionary *returnDict; 
    NSError *error; 
    NSString *jsonString; 
    NSString *authValue; 
    NSString *authStr; 

    NSData *jsonData; 
    NSData *authData; 
    NSURL *aUrl; 
    NSMutableURLRequest *request; 
    NSOperationQueue *queue; 


    jsonData = [NSJSONSerialization dataWithJSONObject:postDict 
                 options:NSJSONWritingPrettyPrinted 
                 error:&error]; 

    if (! jsonData) { 
     NSLog(@"Got an error: %@", error); 
    } else { 
     jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 
    } 

    aUrl  = [NSURL URLWithString:@"http://xx.xx-xx.com/xx.php"]; 
    request  = [NSMutableURLRequest requestWithURL:aUrl 
              cachePolicy:NSURLRequestUseProtocolCachePolicy 
              timeoutInterval:60.0]; 

    queue  = [[NSOperationQueue alloc] init]; 
    authStr  = [NSString stringWithFormat:@"%@:%@", @"xx", @"xx"]; 
    authData = [authStr dataUsingEncoding:NSASCIIStringEncoding]; 
    authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodedString]]; 
    [request setValue:authValue forHTTPHeaderField:@"Authorization"]; 
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-type"]; 
    [request setHTTPMethod:@"POST"]; 
    [request setHTTPBody:[jsonString dataUsingEncoding:NSUTF8StringEncoding]]; 

    [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) 
    { 
     NSString *responseBody = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 
     returnDict    = [NSJSONSerialization JSONObjectWithData: [responseBody dataUsingEncoding:NSUTF8StringEncoding] 
                    options: NSJSONReadingMutableContainers 
                    error: &error]; 
     if (completion) { 
      completion(returnDict, error); 
     } 
    }]; 
} 

//User.h 
[_httpPost doHttpRequest:_dbDictionary completion:^(NSDictionary *dict, NSError *error) { 
    NSLog(@"completed") // NEVER GETS FIRED 
}]; 

ответ

9

Похоже, вы пытаетесь выполнить асинхронный процесс (sendAsynchronousRequest) и заставить его вести себя как синхронный процесс (т. Е. Вы хотите его подождать). Вы не должны этого делать. Вы должны принять асинхронные шаблоны, а не бороться с ними.

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

Обычным способом будет давать свои собственные методы их собственные блоки завершения, а затем вызвать эти блоки в completionHandler из sendAsynchronousRequest, что-то вроде:

- (void)performHttpRequest:(NSDictionary *)postDict completion:(void (^)(NSDictionary *dictionary, NSError *error))completion 
{ 
    // prepare the request 

    // now issue the request 

    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { 
     if (error) { 
      if (completion) 
       completion(data, error); 
     } else { 
      NSString *responseBody = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 
      returnDict    = [NSJSONSerialization JSONObjectWithData:data 
                    options: NSJSONReadingMutableContainers 
                     error: &error]; 
      if (completion) 
       completion(returnDict, error); 
    }]; 
} 

Теперь, когда вы хотите, чтобы выполнить вашу просьбу, вам просто сделать:

[self performHttpRequest:someDictionary completion:^(NSDictionary *dictionary, NSError *error) { 
    if (error) { 
     // ok, handle the error here 
    } else { 
     // ok, use the `dictionary` results as you see fit here 
    } 
]; 

Отметим, что метод, который вызывает этот performHttpRequest (давайте представим, вы назвали его из loadModelFrom) в настоящее время ведет себя асинхронно, себя. Таким образом, вы можете снова использовать этот шаблон завершения-блокировки, например. добавив свой собственный параметр блока completion в loadModelFrom, а затем вызовите этот блок в обработчике завершения. loadModelFrom переходит к performHttpRequest.

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


Update:

Пересмотренный образец кода (в основном) отлично работает для меня. Увидев свой пересмотренный вопрос, несколько наблюдений:

  1. Я не знаком с этим методом base64EncodedString. В iOS 7 существует собственный метод base64EncodedStringWithOptions (или для более ранних версий iOS используется base64Encoding). Или вы используете стороннюю базу-64 NSData?

  2. Нет смысла создавать jsonString, чтобы преобразовать его обратно в NSData. Просто используйте jsonData в вашем запросе.

    То же самое верно с responseBody: зачем конвертировать в строку только для преобразования обратно в NSData?

  3. Нет смысла указывать returnDict как __block за пределами блока sendAsynchronousRequest. Просто определите его внутри этого блока, и тогда квалификатор __block больше не нужен.

  4. Зачем создавать NSOperationQueue для completionHandler из sendAsynchronousRequest?Если я делаю что-то действительно медленное, что заслуживает внимания в фоновом режиме, я просто использую [NSOperationQueue mainQueue], потому что вы неизменно хотите обновить модель приложения или пользовательский интерфейс (или и то и другое), и вы хотите делать такие вещи в главной очереди ,

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

  5. Кстати, в sendAsynchronousRequest вы не проверяете, удалось ли выполнить запрос, прежде чем продолжить с JSONObjectWithData. Если запрос не удался, теоретически вы можете потерять объект NSError, который он вернул. Вы действительно должны проверить, чтобы запрос был выполнен, прежде чем пытаться его проанализировать.

    Аналогичным образом, когда вы изначально dataWithJSONObject параметров в postDict, вам действительно нужно проверить успех, а если нет, сообщите об ошибке и закройте.

  6. Я замечаю, что вы используете опцию NSJSONReadingMutableContainers. Если вам действительно нужен изменяемый ответ, я бы предложил сделать это явным в ваших параметрах блока (заменив все ссылки NSDictionary на NSMutableDictionary). Я предполагаю, что вам действительно не нужно его менять, поэтому я рекомендую удалить опцию NSJSONReadingMutableContainers.

    Аналогичным образом, при создании JSON вам не нужно использовать опцию NSJSONWritingPrettyPrinted. Это делает запрос ненужным больше.

Объединяя все это, что дает:

-(void)performHttpRequest:(NSDictionary *)postDict completion:(void(^)(NSDictionary *dict, NSError *error))completion { 
    NSError *error; 
    NSString *authValue; 
    NSString *authStr; 

    NSData *jsonData; 
    NSData *authData; 
    NSURL *aUrl; 
    NSMutableURLRequest *request; 

    jsonData = [NSJSONSerialization dataWithJSONObject:postDict options:0 error:&error]; 

    if (!jsonData) { 
     if (completion) 
      completion(nil, error); 
     return; 
    } 

    aUrl  = [NSURL URLWithString:@"...."]; 
    request  = [NSMutableURLRequest requestWithURL:aUrl 
              cachePolicy:NSURLRequestUseProtocolCachePolicy 
             timeoutInterval:60.0]; 

    authStr  = [NSString stringWithFormat:@"%@:%@", @"xx", @"xx"]; 
    authData = [authStr dataUsingEncoding:NSASCIIStringEncoding]; 
    if ([authData respondsToSelector:@selector(base64EncodedStringWithOptions:)]) 
     authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodedStringWithOptions:0]]; 
    else 
     authValue = [NSString stringWithFormat:@"Basic %@", [authData base64Encoding]]; // if only supporting iOS7+, you don't need this if-else logic and you can just use base64EncodedStringWithOptions 
    [request setValue:authValue forHTTPHeaderField:@"Authorization"]; 
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-type"]; 
    [request setHTTPMethod:@"POST"]; 
    [request setHTTPBody:jsonData]; 

    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) 
    { 
     if (!data) { 
      if (completion) 
       completion(nil, error); 
      return; 
     } 

     NSError *parseError = nil; 
     NSDictionary *returnDict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError]; 

     if (completion) { 
      completion(returnDict, parseError); 
     } 
    }]; 
} 

И если это вызывается из другого метода, который необходимо обработать тот факт, что это происходит асинхронно, то он будет использовать блок завершения картина тоже:

- (void)authenticateUser:(NSString *)userid password:(NSString *)password completion:(void (^)(BOOL success))completion 
{ 
    NSDictionary *dictionary = @{ ... }; 

    [self performHttpRequest:dictionary completion:^(NSDictionary *dict, NSError *error) { 
     if (error) { 
      completion(NO); 
      return; 
     } 

     // now validate login by examining resulting dictionary 

     BOOL success = ...; 

     // and call this level's completion block 

     completion(success); 
    }]; 
} 

Затем контроллер представления может получить доступ к этому методу что-то вроде:

// maybe add UIActivityIndicatorView here 

[self.userModel authenticateUser:self.userTextField.text password:self.passwordTextField.text completion:^(BOOL success) { 
    // remove UIActivityIndicatorView here 
    if (success) { 
     // do whatever you want if everything was successful, maybe segue to another view controller 
    } else { 
     // show the user an alert view, letting them know that authentication failed and let them try again 
    } 
}]; 
+0

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

+0

Привет @Rob - Несчастливо это не работает. Я изменил свой первоначальный пост, так что теперь вы можете видеть, как это происходит на данный момент, и на самом деле это не работает. – Musterknabe

+0

@Musterknabe Когда вы говорите «на самом деле не работает», что происходит? Я просто проверил ваш код на моем сервере (только заменив метод base-64), и он работал нормально. Я бы предложил установить точку останова и один шаг по вашему коду и выяснить, где он поступил неправильно. Если это не сбой, но вы не видите его полным, единственное, что я могу себе представить, это если ваша переменная _httpPost не была должным образом создана. Но код работает нормально. Если это произошло, обновите свой вопрос и сообщите нам, что именно произошло. См. Мой пересмотренный ответ для некоторых подробных наблюдений за вашим кодом. – Rob

0

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

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

Надеюсь, это было бы полезно.

0

Если вы хотите дождаться запроса, вам не следует использовать sendAsynchronousRequest. Вместо этого используйте sendSynchonousRequest. Вот где оно сделано для:

NSURLResponse *response; 
NSError * error;  
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; 

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

Смежные вопросы