可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am developing a networkUtil for my project, I need a method that gets a url and returns the JSON received from that url using NSURLSessionDataTask to get a JSON from server. the method is the following:
+ (NSDictionary*) getJsonDataFromURL:(NSString *)urlString{
__block NSDictionary* jsonResponse;
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(@"%@", jsonResponse);
}];
[dataTask resume];
return jsonResponse;
}
The problem is that the completionHandler inside my method and the method itself are run on different threads and in the last line the jsonResponse is always nil
How should I set jsonResponse with returned json from urlString?
What is the best practice for this issue?
回答1:
Block that is running in NSURLSession is running on different thread - your method doesn't wait block to finish.
You have two options here
First one. Send NSNotification
+ (void) getJsonDataFromURL:(NSString *)urlString{
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary* jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(@"%@", jsonResponse);
[[NSNotificationCenter defaultCenter] postNotificationName:@"JSONResponse" object:nil userInfo:@{@"response" : jsonResponse}];
}];
[dataTask resume];
}
Second one. Past completion block to this utility method
+ (void) getJsonDataFromURL:(NSString *)urlString
completionBlock:(void(^)(NSDictionary* response))completion {
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary* jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(@"%@", jsonResponse);
completion(jsonResponse);
}];
[dataTask resume];
}
回答2:
Some people could say it is a horrible advice but you also can download your data synchronously. It should be done in a background queue. It is not a best practice but for some cases (like a command line utility, non-critical background queue) it is ok.
NSURLSession does not have synchronous download method but you can easily bypass it with semaphore:
+ (NSDictionary*) getJsonDataFromURL:(NSString *)urlString{
__block NSDictionary* jsonResponse;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); // Line 1
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(@"%@", jsonResponse);
dispatch_semaphore_signal(semaphore); // Line 2
}];
[dataTask resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // Line 3
return jsonResponse;
}
NSURLSession has a delegateQueue property which is used for "delegate method calls and completion handlers related to the session". By default NSURLSession always creates a new delegateQueue during initialisation. But if you set a NSURLSession's delegation queue yourself make sure you do not call your method in the same queue since it will block it.
回答3:
It is obviously Method will return before Block is completed. Because that is main purpose of the block.
You needs to change something like this :
NSDictionary* jsonResponse;
+ (NSDictionary*) getJsonDataFromURL:(NSString *)urlString{
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
self.jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(@"%@", jsonResponse);
[dataTask resume];
// ad observer here that call method to update your UI
}];
}
回答4:
This is the intended behavior of "asynchronous calls". They shall not block the calling thread, but execute the passed block, when the call is done.
Simply add the code that has to be executed after getting the result in the block.
So instead of …
+ (NSDictionary*) getJsonDataFromURL:(NSString *)urlString
{
__block NSDictionary* jsonResponse;
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error)
{
jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(@"%@", jsonResponse);
}];
[dataTask resume];
return jsonResponse;
}
…
NSDictionary* jsonResponde = [self getJsonDataFromURL:url]; // BTW: get infringes the naming rules of Objective-C
// The code that has to be executed is here
… you do this:
+ (void) getJsonDataFromURL:(NSString *)urlString
{
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error)
{
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(@"%@", jsonResponse);
// Place the code to be executed here <-----
}];
[dataTask resume];
}
…
[self getJsonDataFromURL:url]; // BTW: get infringes the naming rules of Objective-C
// No code here any more
If the code executed by -getJsonDataFromURL:
depends on the caller, simply pass it as an argument to the method and execute it at the specified location. If you need help for that, let me know. I will add the code for it.
Another solution is to use a semaphore and wait, until the completion handler is executed. But this will block the UI and is the not-intended way to do it.