等到NSURLConnection sendAsynchronousRequest完成 [英] Wait until NSURLConnection sendAsynchronousRequest is finished

查看:78
本文介绍了等到NSURLConnection sendAsynchronousRequest完成的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have the following problem. I have a Model, called User. When the user now logins with Facebook, my app checks if the user exists already in the database. To not freeze the UI (since I'm coming from Android) I thought to use NSURLConnection sendAsynchronousRequest. What worked at first was the following:

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

My User Model had a method to do the whole task of the AsynchronousRequest and then when finished would set a variable to loading. Then other classes, could simply check with while ( !user.loading ) if the Request was finished or not. The problem that came here to me, was, that now, I had to put this method in every Model. So instead of this, I created a new Class HTTPPost. This class now has the method that gets an NSDictionary passed and returns one. This works ALMOST. The problem I was now encountering is, that I couldn't really determine if the process was finished or not. So I started to create a new class called Globals and use global Variable loading. But the global variable is ALWAYS NO. So, what would be the best way to do this?

这是我的代码:

这是哪里我检查用户并加载它。 resultDictionary NSDictionary ,其中所有内容都被加载,但始终是 nil

This is where I check for the user and load it. resultDictionary is the NSDictionary where everything gets loaded in, but is always 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。我以前做过的工作如下。

The problem now, is, that, since I'm sending an AsynchronousRequest, the resultDictionary is always nil. What I did before and worked was the following.

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

In my Model I had the HTTP Request and a variable named loading. Now I set loading to false until the response has been made into a NSDictionary

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

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

But, then I had another problem. I had to do this in all my Models again... So I created a new Class that subclasses NSObject, that has the asynchronousRequest. This is the whole 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;
}

如你所见,我现在有一个名为的变量装载。这是一个全局变量。但不知何故,变量始终为NO。

As you can see I have now a variable called loading. It is a global variable. But somehow, the variable is always NO.

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

What would be the best way to do this? I hope I'm understandable, I'm new to Objective-C, and English isn't my native language.

UPDATE

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

I modified the code to look like a user provided here, but still not working!

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


推荐答案

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

It seems that you're trying to take an asynchronous process (sendAsynchronousRequest) , and make it behave like a synchronous process (i.e. you appear to want to wait for it). You should not do that. You should to embrace the asynchronous patterns rather than fighting them.

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

The sendAsynchronousRequest method has a completion block that specifies what you want to do when the request is done. Do not try to put the code after the block and (try to) wait for the block to complete, but rather put any of your code that is dependent upon the completion of the network request inside the completion block, or have the completion block call your code.

一种常见的方法是给你自己的方法自己的完成块,然后调用那些 completionHandler sendAsynchronousRequest 中的块,类似于:

A common way would be to give your own methods their own completion blocks and then call those blocks in the completionHandler of sendAsynchronousRequest, something like:

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

现在,当您想要执行请求时,您只需:

Now, when you want to perform your request, you simply do:

[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 调用它)现在本身就是异步行为。因此,您可能希望再次使用此完成块模式,例如将您自己的完成块参数添加到 loadModelFrom ,然后在完成处理程序中调用该块loadModelFrom 传递给 performHttpRequest

Note, the method that calls this performHttpRequest (let's imagine you called it from loadModelFrom) now behaves asynchronously, itself. So you might want to employ this completion-block pattern again, e.g. adding your own completion block parameter to loadModelFrom, and then invoke that block in the completion handler loadModelFrom passes to performHttpRequest.

但希望你明白这一点:永远不要试图等待一个完成块,但只需将其完成后放入该块中的任何内容。无论您使用AFNetworking(我建议使用),还是继续使用 sendAsynchronousRequest ,这都是您应该熟悉的非常有用的模式。

But hopefully you get the idea: Never try to wait for a completion block, but rather just put inside that block anything you want it to do when its done. Whether you use AFNetworking (which I'd advise), or continue to use sendAsynchronousRequest, this is a very useful pattern with which you should become familiar.

更新:

修订后的代码示例(主要是)对我很有用。看到你修改过的问题,有几点意见:

The revised code sample (largely) works great for me. Seeing your revised question, a couple of observations:


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

  1. I am not familiar with this base64EncodedString method. In iOS 7, there is the native base64EncodedStringWithOptions method (or for earlier iOS versions use base64Encoding). Or are you using a third party base-64 NSData category?

创建<没有意义code> jsonString ,然后才将其转换回 NSData 。只需在您的请求中使用 jsonData

There's no point in creating jsonString, only to then convert it back to a NSData. Just use jsonData in your request.

responseBody 同样如此c $ c>:为什么只转换为字符串以转换回 NSData

The same is true with responseBody: Why convert to string only to convert back to NSData?

returnDict 定义为 __ block sendAsynchronousRequest 块之外。只需在该块中定义它,然后就不再需要 __ block 限定符。

There's no point in having returnDict to be defined as __block outside the sendAsynchronousRequest block. Just define it inside that block and the __block qualifier is then no longer necessary.

为什么要为 completionHandler NSOperationQueue > sendAsynchronousRequest ?除非我做的事情非常慢,值得在后台队列上运行,否则我只使用 [NSOperationQueue mainQueue] ,因为你总是希望更新应用程序的模型或UI(或者两者),你想在主队列上做那种东西。

Why create a NSOperationQueue for the completionHandler of sendAsynchronousRequest? Unless I'm doing something really slow that merits running on a background queue, I just use [NSOperationQueue mainQueue], because you invariably want to update the app's model or UI (or both), and you want to do that sort of stuff on the main queue.

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

The request still runs asynchronously but the queue parameter just specifies which queue the completion block will run on.

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

By the way, in sendAsynchronousRequest, you aren't checking to see if the request succeeded before proceeding with JSONObjectWithData. If the request failed, you could theoretically be losing the NSError object that it returned. You really should check to make sure the request succeeded before you try to parse it.

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

Likewise, when you originally dataWithJSONObject the parameters in postDict, you really should check for success, and if not, report the error and quit.

我注意到你正在使用 NSJSONReadingMutableContainers 选项。如果你真的需要一个可变的响应,我建议在你的块参数中明确表示(用 NSMutableDictionary NSDictionary 引用$ C>)。我假设你并不真的需要它是可变的,因此我建议删除 NSJSONReadingMutableContainers 选项。

I notice that you're using the NSJSONReadingMutableContainers option. If you really need a mutable response, I'd suggest making that explicit in your block parameters (replacing all the NSDictionary references with NSMutableDictionary). I assume you don't really need it to be mutable, so I therefore recommend removing the NSJSONReadingMutableContainers option.

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

Likewise, when creating the JSON, you don't need to use the NSJSONWritingPrettyPrinted option. It only makes the request unnecessary larger.

结合所有这些,产生:

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

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

And if this is being called from another method that needs to handle the fact that this is happening asynchronously, then it would employ a completion block pattern, too:

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

然后视图控制器可以通过以下方式访问该方法:

Then the view controller might access that method with something like:

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

这篇关于等到NSURLConnection sendAsynchronousRequest完成的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆