![](/img/trans.png)
[英]How to detect if NSURLConnection sendAsynchronousRequest is finished
[英]Wait until NSURLConnection sendAsynchronousRequest is finished
我有以下问题。 我有一个名为User的模型。 当用户现在使用Facebook登录时,我的应用程序会检查用户是否已存在于数据库中。 为了不冻结UI(因为我来自Android),我想使用NSURLConnection sendAsynchronousRequest
。 最初起作用的是以下内容:
我的用户模型有一个方法来完成AsynchronousRequest
的整个任务,然后在完成时将一个变量设置为加载。 然后其他类,如果请求完成,可以简单地检查while ( !user.loading )
。 我遇到的问题是,现在,我必须将此方法放在每个模型中。 因此,我创建了一个新的类HTTPPost
而不是这个。 该类现在具有传递NSDictionary
并返回一个的方法。 这几乎可以工作。 我现在遇到的问题是,我无法确定流程是否已完成。 所以我开始创建一个名为Globals
的新类并使用全局变量loading
。 但全局变量总是没有。 那么,最好的方法是什么?
这是我的代码:
这是我检查用户并加载它的地方。 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总是为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的新手,英语不是我的母语。
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
}];
您似乎正在尝试采用异步进程( sendAsynchronousRequest
),并使其行为类似于同步进程(即您似乎希望等待它)。 你不应该这样做。 你应该接受异步模式而不是对抗它们。
sendAsynchronousRequest
方法有一个完成块,它指定在请求完成时您要执行的操作。 不要试图把代码块后 (尝试)等待块来完成,而是把你的任何代码,依赖完成块内的网络请求完成后的,或有完成块调用你的代码。
一种常见的方法是为自己的方法提供自己的完成块,然后在sendAsynchronousRequest
的completionHandler
中调用这些块,如:
- (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
,这都是您应该熟悉的非常有用的模式。
更新:
修改后的代码示例(很大程度上)对我很有用。 看到您修改过的问题,有几点意见:
我不熟悉这个base64EncodedString
方法。 在iOS 7中,存在本机base64EncodedStringWithOptions
方法(或者对于早期iOS版本使用base64Encoding
)。 或者您使用的是第三方base-64 NSData
类别?
创建jsonString
没有意义,只能将其转换回NSData
。 只需在请求中使用jsonData
。
responseBody
也是如此:为什么只转换为字符串才能转换回NSData
?
有在不具有点returnDict
被定义为__block
外部sendAsynchronousRequest
块。 只需在该块中定义它,然后就不再需要__block
限定符。
为什么要为NSOperationQueue
的completionHandler
创建一个sendAsynchronousRequest
? 除非我做的事情非常慢,值得在后台队列上运行,我只使用[NSOperationQueue mainQueue]
,因为你总是希望更新应用程序的模型或UI(或两者),并且你想要做那种东西主队列。
请求仍然以异步方式运行,但queue
参数只指定完成块将在哪个队列上运行。
顺便说一句,在sendAsynchronousRequest
,在继续使用JSONObjectWithData
之前,您不会检查请求是否成功。 如果请求失败,理论上可能会丢失它返回的NSError
对象。 在尝试解析之前,您确实应该检查以确保请求成功。
同样,当你最初使用postDict
dataWithJSONObject
参数时,你应该检查是否成功,如果没有,则报告错误并退出。
我注意到你正在使用NSJSONReadingMutableContainers
选项。 如果你真的需要一个可变的响应,我建议在你的块参数中明确表示(用NSMutableDictionary
替换所有的NSDictionary
引用)。 我假设你并不真的需要它是可变的,因此我建议删除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
}
}];
在看到您添加特定代码以处理请求及其响应后,我会指出您应该尝试使用AFNetworking 。 它抽象出大量的锅炉板代码。
正如你所提到的,你是obj-c的新手,可能需要一些时间来了解AFNetworking,但从长远来看,它会为你节省很多麻烦。 此外,它是广泛使用的网络相关的开源之一。
我希望这会有所帮助。
如果要等待请求,则不应使用sendAsynchronousRequest。 请改用sendSynchonousRequest。 这就是它的用途:
NSURLResponse *response;
NSError * error;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
但是,在进行同步调用时,UI被阻止。 我怀疑这是不是你想要的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.