等到NSURLConnection sendAsynchronousRequest完成

我有以下问题。 我有一个模型,称为用户。 当用户现在用Facebooklogin时,我的应用会检查用户是否已经存在于数据库中。 为了不冻结UI(因为我来自Android),我想使用NSURLConnection sendAsynchronousRequest 。 起初有效的是以下几点:

我的用户模型有一个方法来完成AsynchronousRequest的整个任务,然后完成时将设置一个variables加载。 然后其他类,如果请求完成,可以简单地检查while ( !user.loading ) 。 这个问题到现在为止,我不得不把这个方法放在每一个模型中。 所以,而不是这个,我创build了一个新的类HTTPPost 。 这个类现在有一个方法,它传递一个NSDictionary并返回一个。 这工作几乎。 我现在遇到的问题是,我不能确定过程是否完成。 于是我开始创build一个名为Globals的新类,并使用全局variablesloading 。 但是全局variables总是不变 那么,最好的办法是什么呢?

这是我的代码:

这是我检查用户并加载它的地方。 resultDictionaryNSDictionary中的一切都被加载,但始终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的variables。 现在我设置加载为false,直到响应已经成为一个NSDictionary

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

但是,我还有另外一个问题。 我不得不在我的所有模型中再次做到这一点…所以我创build了一个新的类,NSObject的子类,它具有asynchronous请求。 这是整个要求

 -(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的variables。 这是一个全局variables。 但不知何故,这个variables永远是NO。

什么是最好的方法来做到这一点? 我希望我可以理解,我是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 }]; 

看来你正在尝试一个asynchronous进程( sendAsynchronousRequest ),并使其行为像一个同步进程(即你似乎想要等待它)。 你不应该这样做。 您应该拥抱asynchronous模式,而不是与之搏斗。

sendAsynchronousRequest方法有一个完成块,用于指定完成请求时要执行的操作。 不要尝试在代码块后面放置代码并且(尝试)等待代码块完成,而是将完成代码块中依赖于networking请求完成的任何代码,或者完成块调用你的代码。

一个常见的方法是给自己的方法自己的完成块,然后在sendAsynchronousRequestcompletionHandler中调用这些块,如下所示:

 - (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调用它)的方法现在本身是asynchronous的。 所以你可能想要再次使用这个完成块模式,比如把你自己的completion块参数join到loadModelFrom ,然后在完成处理程序loadModelFrom传递给performHttpRequest调用这个块。

但希望你明白:不要试图等待一个完成块,而应该在块完成之后把它放在里面。 无论您使用AFNetworking(我build议),或继续使用sendAsynchronousRequest ,这是一个非常有用的模式,你应该熟悉。


更新:

修改后的代码示例(很大程度上)对我很有帮助。 看到你修改后的问题,有几点意见:

  1. 我不熟悉这个base64EncodedString方法。 在iOS 7中,有本地的base64EncodedStringWithOptions方法(或者更早的iOS版本使用base64Encoding )。 或者你使用的是第三方base-64 NSData类别?

  2. 创buildjsonString没有意义,只能将其转换回NSData 。 只要在你的请求中使用jsonData

    responseBody也是如此:为什么只能将string转换回NSData

  3. returnDict定义为__blocksendAsynchronousRequest块之外是sendAsynchronousRequest 。 只需在该块内定义它,然后不再需要__block限定符。

  4. 为什么要为NSOperationQueuecompletionHandler创build一个sendAsynchronousRequest ? 除非我正在做一些非常慢的,值得在后台队列上运行的东西,我只是使用[NSOperationQueue mainQueue] ,因为你总是想更新应用程序的模型或UI(或两者),并且你想要做那样的事情主队列。

    该请求仍然是asynchronous运行的,但是queue参数只是指定了完成块将在哪个队列上运行。

  5. 顺便说一句,在sendAsynchronousRequest ,在继续使用JSONObjectWithData之前,您没有检查是否成功。 如果请求失败,理论上可能会丢失返回的NSError对象。 在尝试parsing之前,确实应该检查以确保请求成功。

    同样,当你最初的dataWithJSONObject参数在postDict ,你真的应该检查成功,如果没有,报告错误并退出。

  6. 我注意到你正在使用NSJSONReadingMutableContainers选项。 如果你确实需要一个可变的响应,我会build议在你的块参数中明确地表示(用NSMutableDictionaryreplace所有的NSDictionary引用)。 我假设你并不需要它是可变的,所以我build议删除NSJSONReadingMutableContainers选项。

    同样,在创buildJSON时,您不需要使用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); } }]; } 

如果从另一个需要处理asynchronous事件的方法调用它,那么它也会使用一个完成块模式:

 - (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 } }]; 

在看到你添加特定的代码来处理请求和响应之后,我会指出你应该尝试使用AFNetworking 。 它提取了大量的锅炉代码。

正如你所提到的,你是obj-c的新手,可能需要一些时间才能理解AFNetworking,但从长远来看,这将为你节省很多头痛。 此外,它是networking相关的东西广泛使用的开源之一。

我希望这会有所帮助。

如果你想等待一个请求,那么你不应该使用sendAsynchronousRequest。 改用sendSynchonousRequest。 这就是它的目的:

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

但是,当进行同步呼叫时UI被阻塞。 我怀疑这是不是你想要的。