等到NSURLConnection sendAsynchronousRequest完成 [英] Wait until NSURLConnection sendAsynchronousRequest is finished
问题描述
我有以下问题。我有一个名为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:
-
我不熟悉这个
base64EncodedString
方法。在iOS 7中,存在本机base64EncodedStringWithOptions
方法(或者对于早期iOS版本使用base64Encoding
)。或者您使用的是第三方基础-64NSData
类别?
I am not familiar with this
base64EncodedString
method. In iOS 7, there is the nativebase64EncodedStringWithOptions
method (or for earlier iOS versions usebase64Encoding
). Or are you using a third party base-64NSData
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 $ c替换所有
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屋!