iOS: Making multiple NSURLSession request - ios

I need to make multiple http request but I need to wait for response of the request to move on to the next one but I can not figure out how to do it. Does any body knows how can I do this without using dispatch_semaphore
This is my code:
-(void)multipleReques:(NSArray*)arrayUrl
{
__block NSInteger *countFailures = 0;
for (NSString *urlStr in arrayUrl)
{
NSURL *URL = [NSURL URLWithString:urlStr];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
if (error)
{
countFailures++;
}
}];
[task resume];
}
}
I'll really appreciate your help.

Here's one way to do it: (Just a quick demo, not sure if it executed at all)
// Global variables somewhere...
NSUInteger progress;
NSUInteger failures;
- (void)myFunction
{
// ...
progress = 0;
failures = 0;
[self makeRequestWithURLs:#[...]]; // an array of NSURLs.
// ...
}
- (void)makeRequestWithURLs:(NSURL *)arrayOfURLs
{
if ([arrayOfURLs count] <= progress) {
return;
}
NSURLRequest *request = [NSURLRequest requestWithURL:arrayOfURLs[progress]];
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
dataTaskWithRequest:request completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error)
{
if (error) {
failures++;
} else {
progress++;
[self makeRequestWithURLs:arrayOfURLs];
}
}];
[task resume];
}

Related

how to call again same APIs when response is other then status code 200 or error (some time)

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];
}

Showing spinner using spinKit in iOS while fetching JSON

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;
}

Trying to understand asynchronous calls

I have this method
- (NSString*) createUserWithName:(NSString*)TheName
{
NSURL *URL =someUrlthatIncludesTheName
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
[request setHTTPMethod:#"GET"];
NSURLSession *session = [NSURLSession sharedSession];
NSURL *URL = [NSURL URLWithString:url];
NSURLSessionTask *task = [session dataTaskWithURL:URL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (response) {
NSError* error = nil;
NSArray *output = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&error];
myID = [[output objectAtIndex:0] objectForKey:#"UserID"];
}
}];
[task resume];
return myID;
}
and another method
-(void)doSomethingWith: (NSString*) anID
Somewhere in my code, I call these methods subsequently, like this:
[self createUserWithName:#"John"];
[self doSomethingWith:myID];
However, due to the fact that the NSURLSession in createUserWithName: is asynchronous, doSomethingWith: is fired with myID = (null).
What is the best way to approach this problem, without necessarily falling back to deprecated synchronous NSURLConnection?
Thanks in advance
The workflow is supposed to be
- (void)createUserWithName:(NSString*)TheName
{
NSURL *URL =someUrlthatIncludesTheName
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
[request setHTTPMethod:#"GET"];
NSURLSession *session = [NSURLSession sharedSession];
NSURL *URL = [NSURL URLWithString:url];
NSURLSessionTask *task = [session dataTaskWithURL:URL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (response) {
NSError* error = nil;
NSArray *output = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&error];
myID = [[output objectAtIndex:0] objectForKey:#"UserID"];
[self doSomethingWith:myID];
}
}];
[task resume];
}
And the call is just
[self createUserWithName:#"John"];
The method doSomethingWith: is asynchronously executed in the completion block.
Alternatively use a custom completion block
- (void)createUserWithName:(NSString *)theName completion:^(NSString *identifier)completion
{
NSURL *URL =someUrlthatIncludesTheName
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
[request setHTTPMethod:#"GET"];
NSURLSession *session = [NSURLSession sharedSession];
NSURL *URL = [NSURL URLWithString:url];
NSURLSessionTask *task = [session dataTaskWithURL:URL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (response) {
NSError* error = nil;
NSArray *output = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&error];
myID = [[output objectAtIndex:0] objectForKey:#"UserID"];
completion(myID);
}
}];
[task resume];
}
and call it with
[self createUserWithName:#"John" completion:^(NSString *identifier) {
[self doSomethingWith:identifier];
}];

Nested NSURLSessionDataTask for two consecutive HTTP GETs

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.

How to retry a block based URL Request

I am fetching data using iOS7's new URL request methods, like so:
NSMutableURLRequest *request = [NSMutableURLRequest
requestWithURL:[NSURL URLWithString:[self.baseUrl
stringByAppendingString:path]]];
NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
NSUInteger responseStatusCode = [httpResponse statusCode];
if (responseStatusCode != 200) {
// RETRY (??????)
} else
completionBlock(results[#"result"][symbol]);
}];
[dataTask resume];
Unfortunately, from time to time I get HTTP responses indicating the server is not reachable (response code != 200) and need to resend the same request to the server.
How can this be done? How would I need to complete my code snippet above where my comment // RETRY is?
In my example I call the completion block after a successful fetch.
But how can I send the same request again?
Thank you!
It is better to have a retry counter to prevent your method from running forever:
- (void)someMethodWithRetryCounter:(int) retryCounter
{
if (retryCounter == 0) {
return;
}
retryCounter--;
NSMutableURLRequest *request = [NSMutableURLRequest
requestWithURL:[NSURL URLWithString:[self.baseUrl
stringByAppendingString:path]]];
__weak __typeof(self)weakSelf = self;
NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
NSUInteger responseStatusCode = [httpResponse statusCode];
if (responseStatusCode != 200) {
[weakSelf someMethodWithRetryCounter: retryCounter];
} else
completionBlock(results[#"result"][symbol]);
}];
[dataTask resume];
}
It should be called following way:
[self someMethodWithRetryCounter:5];
Put your request code in a method and call it again in a dispatch_async block ;)
- (void)requestMethod {
NSMutableURLRequest *request = [NSMutableURLRequest
requestWithURL:[NSURL URLWithString:[self.baseUrl
stringByAppendingString:path]]];
__weak typeof (self) weakSelf = self;
NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
NSUInteger responseStatusCode = [httpResponse statusCode];
if (responseStatusCode != 200) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul), ^{
[weakSelf requestMethod];
});
} else
completionBlock(results[#"result"][symbol]);
}];
[dataTask resume];
}

Resources