I have this method for getting JSON data from a URL:
-(void)getJsonResponse:(NSString *)urlStr success:(void (^)(NSDictionary *responseDict))success failure:(void(^)(NSError* error))failure
{
NSURLSession *session = [NSURLSession sharedSession];
NSURL *url = [NSURL URLWithString:urlStr];
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//NSLog(#"%#",data);
if (error) {
failure(error);
NSLog(#"Error: %#", [error localizedDescription]);
}
else {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
//NSLog(#"%#",json);
success(json);
}
}];
[dataTask resume];
}
In myViewController, viewWillAppear I call this method as such:
NSString * URLString = #"my.valid.url";
[self getJsonResponse:URLString success:^(NSDictionary *result) {
//here some code when succesful
} failure:^(NSError *error) {
NSLog(#"Something terrible happened");
}];
}
That works fine, but only ONCE:
When I leave myViewController, and enter it again,
viewWillAppear is called, and subsequentially
[self getJsonResponse:... is called
my code in the success block is executed
However: monitoring the network activity with Charles, I notice, that no call is made to my.valid.url.
What gives? Should I invalidated the shared session? If so, when?
set NSURLSessionConfiguration chachePolicy to NSURLRequestReloadIgnoringCacheData and try again. Here is the good resource to understand about Http-caching. Read documentation given in apple guide as well.
NSURLSessionConfiguration *config = NSURLSessionConfiguration.defaultSessionConfiguration;
config.requestCachePolicy = NSURLRequestReloadIgnoringCacheData;
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
Related
i am using APIs call in my iPhone (objective C) app. my APIS call returns Status code 200, but some time it does return 405 response. when i restart my app i do get proper response.
how to call back same API when i get other then 200 response or if i get error...so that i don't have to restart app again.
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
NSString *authValue = [NSString stringWithFormat:#"Bearer %#",
[arrTokenData valueForKey:#"Token"]];
//Configure session with common header fields
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.HTTPAdditionalHeaders = #{#"Authorization": authValue};
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSString *url = #"http://test.myserver.am/api/mobile/LookUps/getuserdata";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
if (!error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200)
{
//Process the data
}
else if
{
// how to call back same API
}
}
else
{
// how to call back same API
}
}];
[task resume];
Thanks in advance
I considered as you created a generic method of network call. If you will get an error or other than 200 response. Just take the request details from your request and call that generic method with that request(URL).
Sample code:
NSURLSessionDataTask *DataTask = [session dataTaskWithRequest:theRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
NSHTTPURLResponse *serverResponse = (NSHTTPURLResponse*)response;
if (error == nil) {
if (serverResponse.statusCode == 200) {
NSString *theXML = [[NSString alloc] initWithBytes:
[data bytes] length:[data length] encoding:NSUTF8StringEncoding];
NSLog(#"%#", theXML);
} else {
NSLog(#"%#, %#", serverResponse.URL, serverResponse.allHTTPHeaderFields);
//Call the generic network call method with the above details again
}
} else {
NSLog(#"%#", error. localizedDescription);
NSLog(#"%#, %#", serverResponse.URL, serverResponse.allHTTPHeaderFields);
//Call the generic network call method with the above details again
}
}];
[DataTask resume];
Create new thread and execute the failed requests.
Create one method and write your code in that method and call same method on error like below
- (void)callService {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
NSString *authValue = [NSString stringWithFormat:#"Bearer %#",
[arrTokenData valueForKey:#"Token"]];
//Configure session with common header fields
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.HTTPAdditionalHeaders = #{#"Authorization": authValue};
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSString *url = #"http://test.myserver.am/api/mobile/LookUps/getuserdata";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
if (!error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200)
{
//Process the data
}
else
{
// how to call back same API
[self callService];
}
}
else
{
// how to call back same API
[self callService];
}
}];
[task resume];
}
I am trying to use NSURLSession, I had successfully received token from server, now I have to used that Token to get further details...
I had tried below code to get user data..
but I am getting httpResponse code: 500
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
NSString *authValue = [arrTokenData valueForKey:#"Token"];
//Configure session with common header fields
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.HTTPAdditionalHeaders = #{#"bearer": authValue};
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSString *url = #"http://test.myserver.am/api/mobile/LookUps/getuserdata";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
if (!error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200)
{
NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers|NSJSONReadingAllowFragments error:nil];
//Process the data
}
}
}];
[task resume];
I am using iOS 10 , above code is not working, also I have tried several ways but no any success...
I have tried stackoverflow solution but this is also not working out for me...
Please help me out...
What I am trying to do is, on a click of a button I am fetching data using AFNetworking pod. What i want is after fetching data I want to display NSLog(#"/n /nAfter fetching"); but it is coming before the json data.
Here is the code I have written in IBAction of Button.
dispatch_queue_t queue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{
//Load the json on another thread
NSURL *url = [NSURL URLWithString:finalURL];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc]initWithSessionConfiguration:configuration];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response,id responseObject, NSError *error) {
if (error) {
NSLog(#"\n Error --> %#",error);
}
else{
jsonDictionary = (NSDictionary*) responseObject;
NSLog(#"/n Data :: %#",jsonDictionary);
}
}];
[dataTask resume];
});
NSLog(#"/n /nAfter fetching");
Please provide a nice way of doing it and do correct me If I have gone wrong with it. Thank you.
As AFURLSessionManager makes the Asynchronous operation. YOu will get the data in completion
dispatch_queue_t queue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{
//Load the json on another thread
[self startSpinner];
NSURL *url = [NSURL URLWithString:finalURL];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc]initWithSessionConfiguration:configuration];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response,id responseObject, NSError *error) {
[self stopSpinner];
if (error) {
NSLog(#"\n Error --> %#",error);
}
else{
NSLog(#"/n /nAfter fetching"); //this is where you receive data
jsonDictionary = (NSDictionary*) responseObject;
NSLog(#"/n Data :: %#",jsonDictionary);
}
}];
[dataTask resume];
});
if you want to make synchronous operation using URLSession below method will help you
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request
returningResponse:(__autoreleasing NSURLResponse **)responsePtr
error:(__autoreleasing NSError **)errorPtr {
dispatch_semaphore_t sem;
__block NSData * result;
result = nil;
sem = dispatch_semaphore_create(0);
[[[NSURLSession sharedSession] dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (errorPtr != NULL) {
*errorPtr = error;
}
if (responsePtr != NULL) {
*responsePtr = response;
}
if (error == nil) {
result = data;
}
dispatch_semaphore_signal(sem);
}] resume];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
return result;
}
I am a beginner in Objective C and I am looking to do two consecutive HTTP GETs (one after the other). What I have got so far is that I have a NSURLSessionDataTask inside the completion block of the first NSURLSessionDataTask. This is causing my code to be a bit unreadable, so I was wondering what is a better way to do this?
Here is some sample code:
{
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSMutableURLRequest *url_request_1 = [NSMutableURLRequest requestWithURL:#"some_url_1"];
[url_request_1 setHTTPMethod:#"GET"];
NSURLSessionDataTask *url_task_1 = [session
dataTaskWithRequest:url_request_1
completionHandler:^(NSData *data1,
NSURLResponse *response1,
NSError *error1) {
if(data1 !=nil){
// Evaluate some_url_2 from the response of url_task_1
NSMutableURLRequest *url_request_2 = [NSMutableURLRequest requestWithURL:#"some_url_2"];
[url_request_2 setHTTPMethod:#"GET"];
NSURLSessionDataTask *url_task_2 = [session
dataTaskWithRequest:url_request_2
completionHandler:^(NSData *data2,
NSURLResponse *response2,
NSError *error2) {
if(data2 !=nil){
// Process data here
} else {
// Handle error here.
return;
}
}];
[urlRequest2 resume];
}
else{
// Handle error here
return;
}
}];
[url_task_1 resume];
}
This is made a little less unwieldy by changing your indentation style and using early-exit pattern.
- (void)performRequestsWithCompletion:(void (^ _Nonnull)(NSDictionary *, NSError *))completion {
NSMutableURLRequest *request1 = [NSMutableURLRequest requestWithURL:firstURL];
NSURLSessionDataTask *task1 = [self.session dataTaskWithRequest:request1 completionHandler:^(NSData *data1, NSURLResponse *response1, NSError *error1) {
if (!data1) {
// handle error here, then return
completion(nil, error1);
return;
}
NSMutableURLRequest *request2 = [NSMutableURLRequest requestWithURL:secondURL];
NSURLSessionDataTask *task2 = [self.session dataTaskWithRequest:request2 completionHandler:^(NSData *data2, NSURLResponse *response2, NSError *error2) {
if (!data2) {
// handle error here, then return
completion(nil, error2);
return;
}
// handle parsing `data2` here
NSDictionary *result = ...;
completion(result, nil);
}];
[task2 resume];
}];
[task1 resume];
}
Note, I added a completion handler here, because that's one of the best patterns to let whatever initiated this request that everything is done. Clearly, my block parameters assumed you would be returning a dictionary, so you should change this to be whatever type(s) your routine returns.
Alternatively (and especially if you're doing a lot more than just two successive web service calls), you can break this down into separate methods:
- (void)performRequestsWithCompletion:(void (^ _Nonnull)(NSDictionary *, NSError *))completion {
[self performFirstRequestWithCompletion:completion];
}
- (NSURLSessionTask *)performFirstRequestWithCompletion:(void (^ _Nonnull)(NSDictionary *, NSError *))completion {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:firstURL];
NSURLSessionTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!data || error) {
// Handle error here
completion(nil, error);
return;
}
// Evaluate some_url_2 from the response of url_task_1
[self performSecondRequestWithCompletion:completion];
}];
[task resume];
return task;
}
- (NSURLSessionTask *)performSecondRequestWithCompletion:(void (^ _Nonnull)(NSDictionary *, NSError *))completion {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:secondURL];
NSURLSessionTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!data || error) {
// Handle error here, and return
completion(nil, error);
return;
}
// Process data here
NSDictionary *result = ...;
completion(result, nil);
}];
[task resume];
return task;
}
With this pattern, no matter how many dependent calls you have, you won't end up with the tower of nested blocks.
In the interest of completeness, other patterns that avoid these towers of blocks-within-blocks include NSOperation patterns and third party approaches like futures/promises (e.g. PromiseKit). Those are beyond the scope of this question, but they're worth considering if you're doing this a lot.
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?
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];
}
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.
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
}];
}
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.