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?
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 :
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
Second one. Past completion block to this utility method
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:
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.
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 …
… you do this:
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.