等到NSURLConnection sendAsynchronousRequest完成

时间:2014-02-12 17:25:57

标签: ios objective-c

我有以下问题。我有一个名为User的模型。当用户现在使用Facebook登录时,我的应用程序会检查用户是否已存在于数据库中。为了不冻结UI(因为我来自Android),我想使用NSURLConnection sendAsynchronousRequest。最初起作用的是:

我的用户模型有一个方法来执行AsynchronousRequest的整个任务,然后在完成时将变量设置为加载。然后其他类,可以简单地检查 如果请求已完成,则while ( !user.loading )。我遇到的问题是,现在,我必须将此方法放在每个模型中。因此,我创建了一个新的类HTTPPost而不是这个。此类现在具有传递NSDictionary并返回一个的方法。这几乎可以工作。我现在遇到的问题是,我无法确定流程是否已完成。所以我开始创建一个名为Globals的新类,并使用全局变量loading。但全局变量总是没有。那么,最好的方法是什么?

这是我的代码:

这是我检查用户并加载它的地方。 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总是为nil。我以前做过的工作如下:

在我的模型中,我有HTTP请求和名为loading的变量。现在我将loading设置为false,直到将响应设置为NSDictionary

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

但是,我还有另外一个问题。我必须再次在我的所有模型中执行此操作...所以我创建了一个新的Class,它是NSObject的子类,具有asynchronousRequest。这是整个请求

-(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的变量。这是一个全球变量。但不知何故,变量始终为NO。

最好的方法是什么?我希望我能理解,我是Objective-C的新手,英语不是我的母语。

更新

我将代码修改为看起来像这里提供的用户,但仍然无效!

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

3 个答案:

答案 0 :(得分:9)

您似乎正在尝试进行异步处理(sendAsynchronousRequest),并使其行为类似于同步进程(即您似乎想要等待它)。你不应该这样做。你应该接受异步模式而不是对抗它们。

sendAsynchronousRequest方法有一个完成块,用于指定请求完成后要执行的操作。不要试图在块之后放置代码并(尝试)等待块完成,而是将任何依赖于网络请求完成的代码放入其中完成块,或让完成块调用您的代码。

一种常见的方法是为自己的方法提供自己的完成块,然后在completionHandler的{​​{1}}中调用这些块,例如:

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

但是希望你能得到这个想法:永远不要试图等待一个完成块,而只是把它完成后放在那个块里面你想要它做什么。无论您使用AFNetworking(我建议使用),还是继续使用performHttpRequest,这都是您应该熟悉的非常有用的模式。


<强>更新

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

  1. 我不熟悉这种sendAsynchronousRequest方法。在iOS 7中,存在本机base64EncodedString方法(或者早期iOS版本使用base64EncodedStringWithOptions)。或者您使用的是第三方base-64 base64Encoding类别?

  2. 创建NSData没有意义,只能将其转换回jsonString。只需在请求中使用NSData即可。

    jsonData也是如此:为什么只转换为字符串才能转换回responseBody

  3. NSData定义为returnDict块之外的__block毫无意义。只需在该块内定义它,然后就不再需要sendAsynchronousRequest限定符。

  4. 为什么要为__block的{​​{1}}创建NSOperationQueue?除非我做的事情非常慢,值得在后台队列上运行,我只使用completionHandler,因为你总是想要更新应用程序的模型或UI(或两者),你想要做那种东西在主队列上。

    请求仍然以异步方式运行,但sendAsynchronousRequest参数只指定完成块将在哪个队列上运行。

  5. 顺便说一下,在[NSOperationQueue mainQueue]中,在继续queue之前,您没有检查请求是否成功。如果请求失败,理论上可能会丢失它返回的sendAsynchronousRequest对象。在尝试解析请求之前,您确实应该检查以确保请求成功。

    同样,当您最初JSONObjectWithData NSError中的参数时,您确实应该检查是否成功,如果没有,请报告错误并退出。

  6. 我注意到您使用的是dataWithJSONObject选项。如果你真的需要一个可变的响应,我建议你在你的块参数中明确表示(用postDict替换所有NSJSONReadingMutableContainers引用)。我假设你并不真的需要它是可变的,因此我建议删除NSDictionary选项。

    同样,在创建JSON时,您不需要使用NSMutableDictionary选项。它只会使请求变得更大。

  7. 结合所有这些,产生:

    NSJSONReadingMutableContainers

    如果从另一个需要处理异步发生这种事实的方法调用它,那么它也会使用完成块模式:

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

答案 1 :(得分:0)

在看到您添加特定代码以处理请求及其响应后,我会指出您应该尝试使用AFNetworking。它抽象出了许多锅炉板代码。

正如你所提到的,你是obj-c的新手,可能需要一些时间来了解AFNetworking,但从长远来看,它会为你节省很多麻烦。此外,它是广泛使用的网络相关的开源之一。

我希望这会有所帮助。

答案 2 :(得分:0)

如果要等待请求,则不应使用sendAsynchronousRequest。 请改用sendSynchonousRequest。这就是它的目的:

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

但是,在进行同步调用时会阻止UI。我怀疑这是不是你想要的。