看来您正在尝试采用异步过程(sendAsynchronousRequest
) ,并使其表现得像同步进程(即您似乎想要等待它)。你不应该那样做。您应该拥抱异步模式而不是与之对抗。
The sendAsynchronousRequest
方法有一个完成块,指定请求完成后要执行的操作。不要尝试输入代码after块并(尝试)等待块完成,而是放置依赖于网络请求完成的任何代码inside完成块,或者让完成块调用您的代码。
一种常见的方法是为您自己的方法提供自己的完成块,然后在completionHandler
of 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
传递到performHttpRequest
.
但希望你明白了:永远不要尝试等待完成块,而只是在该块中放入你希望它完成后执行的任何操作。无论您使用 AFNetworking(我建议),还是继续使用sendAsynchronousRequest
,这是一个非常有用的模式,您应该熟悉它。
Update:
修改后的代码示例(很大程度上)对我来说非常有用。看到您修改后的问题,有一些观察结果:
-
我对此不熟悉base64EncodedString
方法。在 iOS 7 中,有原生的base64EncodedStringWithOptions
方法(或者对于早期的 iOS 版本,使用base64Encoding
)。或者您是否使用第三方 base-64NSData
类别?
-
创造没有意义jsonString
,然后将其转换回NSData
。只需使用jsonData
在你的要求中。
同样的道理responseBody
:为什么转换为字符串只是为了转换回NSData
?
-
拥有没有意义returnDict
定义为__block
之外的sendAsynchronousRequest
堵塞。只需在该块内定义它即可__block
那么就不再需要限定符了。
-
为什么要创建一个NSOperationQueue
为了completionHandler
of sendAsynchronousRequest
?除非我正在做一些非常慢的事情,值得在后台队列上运行,否则我只是使用[NSOperationQueue mainQueue]
,因为您总是想要更新应用程序的模型或 UI(或两者),并且您想要在主队列上执行此类操作。
该请求仍然异步运行,但queue
参数只是指定完成块将在哪个队列上运行。
-
顺便说一下,在sendAsynchronousRequest
,在继续之前您不会检查请求是否成功JSONObjectWithData
。如果请求失败,理论上您可能会失去NSError
它返回的对象。在尝试解析请求之前,您确实应该检查以确保请求成功。
同样,当你最初dataWithJSONObject
中的参数postDict
,你确实应该检查是否成功,如果没有,报告错误并退出。
-
我注意到你正在使用NSJSONReadingMutableContainers
选项。如果您确实需要可变响应,我建议在您的块参数中明确说明(替换所有NSDictionary
参考文献与NSMutableDictionary
)。我认为您并不真正需要它是可变的,因此我建议删除NSJSONReadingMutableContainers
option.
同样,创建 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
}
}];