I'm currently trying to execute a method after a NSURLSession completes. The problem is I can't manage in any way the asynchronous nature of its object. I've tried with GCD and NSOperation as suggested in the other questions but nothing changes: after the initialization with dataTaskWithRequest:completionHandeler:, the application starts executing the next method in the program.
Here is the method which realizes the networking:
-(void)sendData{
NSData *JSONdata = [NSJSONSerialization dataWithJSONObject:userInfoToJSON options:0 error:&error];
NSURL *url = [NSURL URLWithString:#"http://mobdev2015.com/register.php"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSString *postLength = [NSString stringWithFormat:#"%lu", (unsigned long)[JSONdata length]];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setHTTPBody:JSONdata];
[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
requestReply = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
}]resume];
});
}
Here is the method instead which calls the one above and where I want to implement a synchronous execution:
-(void)MethodWhichNeedsToBeSync{
//Creating a JSON object..
[self sendData:userInfoToJSON];
//MethodB wants to be executed if and only if sendData is completed
[self MethodB];
}
Thank you for the replies.
The way to execute a method after the completion of an asynchronous method is to have that method called in the completion block (or closure) of the asynchronous method.
For your case, it would look something like this:
[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
requestReply = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
if (!error) {
[self MethodB];
}
}]resume];
});
where the call to be run after successful completion of the asynchronous method is moved into the completion handler.
That would accomplish the execution of MethodB if and only if sendData is completed, as you wanted.
As far as I can tell from the NSURLSession documentation there's no way to use it to make synchronous requests. I assume that's because you really shouldn't be making calls that could block the main queue.
However, if using NSURLSession isn't required you can simply use NSURLConnection instead. You would initialize it with your request and call "sendSynchronousRequest:returningResponse:".
Related
I have looked at a lot of posts about http post requests and I still have not been able to find the answer. My problem is that I need to send one string to my work partners website so it can be accessed using a GET request and used for a leader board/
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
[req setHTTPMethod:#"POST"];
NSData *postData = [revs dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:#"%d",[postData length]];
[req addValue:postLength forHTTPHeaderField:#"Content-Length"];
[req setHTTPBody:postData];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:req
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// Do something with response data here - convert to JSON, check if error exists, etc....
NSLog(response.description);
// NSLog([[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding]);
}];
[task resume];
But the request never goes to the website, it is called ../distances.txt
Can anyone help?
I have
NSString* FBAuthoValue= #"TESTINGCONSTANT";
On click of Load Button ,it call HitLoadAPI
I am calling my API like below HitLoadAPI, now if at my server end my FBAuthoValue is change i need to Hit another API to get refresh value of FBAuthoValue, and set in HitLoadAPI.
1) User hit HitLoadAPI with FBAuthoValue= #"TESTINGCONSTANT" value, but as in server now FBAuthoValue= #"NewTestCode", so it return httpresponsecode 909,on 909 i need to call refreshFBAuthValue api, and put this value back to HitLoadAPI, and the api work correctly.
2) if FBAuthoValue token change in server, need to call refreshFBAuthValue API, and its return value need to set and call the HitLoadAPI again,without knowing the user.
NOTE: i have to hit multiple API one after another, suppose API-1,API-2,API-3 and so on, and if FBAuthoValue, if in any api ,changes in server then need to refresh that FBAuthoValue and then the same API need to be call, without effecting or blocking to user.
I will reward 50 bounty for sure.
Overview: API-1 call, in the meanwhile if token expire,need to call token expire api, and the API-1 will recall again, without user press Load Button again.
Here is my code
-(void)HitLoadAPI
{
NSError *error;
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
NSURL *url = [NSURL URLWithString:#"[JSON SERVER"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
[request addValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request addValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setHTTPMethod:#"POST"];
[request setValue:FBAuthoValue forHTTPHeaderField:FBAUTH];
NSDictionary *mapData = [[NSDictionary alloc] initWithObjectsAndKeys: #"TEST IOS", #"name",
#"IOS TYPE", #"typemap",
nil];
NSData *postData = [NSJSONSerialization dataWithJSONObject:mapData options:0 error:&error];
[request setHTTPBody:postData];
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary* headers = [(NSHTTPURLResponse *)response allHeaderFields];
//NSLog(#" headers =%#",headers);
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
//NSLog(#"response status code: %ld", (long)[httpResponse statusCode]);
if([httpResponse statusCode]==909)
{
FBAuthoValue =[self refreshFBAuthValue];
//what to do here so the current API hit will be call again....
}
}];
[postDataTask resume];
}
-(NSString *) refreshFBAuthValue
{
//hit api to get new refresh token code here need its calling code as well as the block coding cause it response so late which cause return value nil...to HitLoadAPI
return FBaccess_token; //it will return refresh FBaccess_token code
}
You can create a method which you can use to call any api from anywhere in the application, This method takes the parameter specific to an api call, like api url, data which will be included in the body of the request and a completion block which will be called when fbAuthValue is valid.
-(void)HitAPILoadWithFbAuthValue:(NSString*)fbAuthValue apiUrl:(NSString*)apiUrl postData:(NSDictionary*)dict withCompletion:(void (^)(int statusCode,NSURLResponse * apiResponse,NSError * error))completion{
NSError *error;
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
NSURL *url = [NSURL URLWithString:apiUrl];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
[request addValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request addValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setHTTPMethod:#"POST"];
[request setValue: fbAuthValue forHTTPHeaderField:FBAUTH];
//post body with dictionary passed as a parameter
NSData *postData = [NSJSONSerialization dataWithJSONObject:dict options:0 error:&error];
[request setHTTPBody:postData];
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary* headers = [(NSHTTPURLResponse *)response allHeaderFields];
//NSLog(#" headers =%#",headers);
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
//NSLog(#"response status code: %ld", (long)[httpResponse statusCode]);
if([httpResponse statusCode]==909)
{
FBAuthoValue =[self refreshFBAuthValue];
//what to do here so the current API hit will be call again....
if (FBAuthoValue){
[self HitAPILoadWithFbAuthValue:fbAuthValue apiUrl:apiUrl postData:dict withCompletion:completion];
}else{
}
}
//if FbAuthValue is valid, call completion block
completion((int)[httpResponse statusCode],response, error);
}];
[postDataTask resume];
}
Usage
I assume you write this method in a separate class called APIManager. So to call this method first create an instance of the APIManager and call like this:
APIManager *sharedManager = [APIManager sharedInstance]
//call API_1
[sharedManager HitAPILoadWithFbAuthValue:FBAuthValue apiUrl:#"API_1_URL" postData:dict_for_first_api withCompletion:^(int statusCode, NSURLResponse *apiResponse, NSError *error) {
if(error != nil){
//handle error here
}else{
//call API_2
[sharedManager HitAPILoadWithFbAuthValue:FBAuthValue apiUrl:#"API_2_URL" postData:dict_for_second_api withCompletion:^(int statusCode, NSURLResponse *apiResponse, NSError *error) {
}];
}
}];
Since NSURLConnection is depreated I need to move to an NSURLSession. I have a URL and some data I need to input as JSON. Then the result should be JSON coming back. I see something like so:
NSError *error;
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
NSURL *url = [NSURL URLWithString:#"[JSON SERVER"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
[request addValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request addValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setHTTPMethod:#"POST"];
NSDictionary *mapData = [[NSDictionary alloc] initWithObjectsAndKeys: #"TEST IOS", #"name",
#"IOS TYPE", #"typemap",
nil];
NSData *postData = [NSJSONSerialization dataWithJSONObject:mapData options:0 error:&error];
[request setHTTPBody:postData];
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
}];
[postDataTask resume];
I this the correct approach?
My requirements are:
1. Turn my key value pairs into JSON.
2. Pass in the URL and JSON to a reusable function.
3. Get the JSON data returned.
4. Parse the JSON data returned.
Have the callers to your method to provide a completion handler which processes the data returned and update the UI to indicate completion.
You can copy the pattern found in the SDK, as follows:
- (void)makeRequest:(NSString *)param completion:(void (^)(NSDictionary *, NSError *))completion;
Implement it like this:
// in the same scope
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
- (void)makeRequest:(NSString *)param
completion:(void (^)(NSDictionary *, NSError *))completion {
// your OP code goes here, e.g.
NSError *error;
NSMutableURLRequest *request = // maybe the param is the url for this request
// use the already initialized session
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// call the completion handler in EVERY code path, so the caller is never left waiting
if (!error) {
// convert the NSData response to a dictionary
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (error) {
// there was a parse error...maybe log it here, too
completion(nil, error);
} else {
// success!
completion(dictionary, nil);
}
} else {
// error from the session...maybe log it here, too
completion(nil, error);
}
}];
[postDataTask resume];
}
Code that calls this method will look like this:
// update the UI here to say "I'm busy making a request"
// call your function, which you've given a completion handler
[self makeRequest:#"https://..." completion:^(NSDictionary *someResult, NSError *error) {
// here, update the UI to say "Not busy anymore"
if (!error) {
// update the model, which should cause views that depend on the model to update
// e.g. [self.someUITableView reloadData];
} else {
// handle the error
}
}];
Notice a couple things: (1) the return type is void, the caller expects nothing to be returned from this method, and makes no assignment when calling it. The data "returned" is provided as parameters to the completion handler, which is called later, after the asnych request is complete, (2) the signature of the completion handler matches exactly what the caller declared in the completion block ^(NSDictionary *, NSError *), this is just a suggestion, typical for network requests.
Instantiate the NSURLSession and NSMutableURLRequest object:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setHTTPMethod:#"POST"];
[request addValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request addValue:#"application/json" forHTTPHeaderField:#"Accept"];
Turn your key value pairs into JSON:
// choose the right type for your value.
NSDictionary *postDict = #{#"key1": value1, #"key2": value2};
NSData *postData = [NSJSONSerialization dataWithJSONObject:postDict options:0 error:nil];
Make your POST with with the URL and JSON:
[request setURL:[NSURL URLWithString:#"JSON SERVER"];
[request setHTTPBody:postData];
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
}];
[postDataTask resume];
Parse the JSON data returned within the completionHandler above:
if (!error) {
NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
} else {
// error code here
}
responseDict is the parsed data. For example, if the server returns
{
"message":"Your messsage",
"data1":value1,
"data2":value2
}
You can easily get the value for data1 by using
[responseDict objectForKey:#"data1"];
If your want to make another POST with different URL or JSON, just repeat the flow of step 2-4.
Hope my answer helps.
I am calling webservice on mapView drag/regionChange. I want to keep some delay in the web service calls. So I want that whenever the user drags the map multiple times, all previous web service calls should be cancelled and only last drag web service call should be fired.
How do I do this?
Following is my code:
{ .... NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
NSString *postLength = [NSString stringWithFormat:#"%lu",(unsigned long)[data length]];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setHTTPBody:data];
[request setHTTPMethod:#"POST"];
NSMutableDictionary __block *dictResponse;
[[NSOperationQueue mainQueue] cancelAllOperations];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if(connectionError == nil){
dictResponse = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&connectionError];
}
You cannot cancel a sendAsynchronousRequest. If you used a delegate-based NSURLConnection request, then you could cancel it, but (a) that's more coding than you probably want to bother with; (b) NSURLConnection is deprecated, so you should be NSURLSession anyway; and (c) NSURLSession allows you to cancel the task using the NSURLSessionTask reference that is returned to you:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setHTTPBody:data];
[request setHTTPMethod:#"POST"];
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(#"connection error = %#", error);
return;
}
NSError *parseError;
NSDictionary *responseObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&parseError];
if (!responseObject) {
NSLog(#"parse error = %#", parseError);
}
dispatch_async(dispatch_get_main_queue(), ^{
// use response here; e.g., updating UI or model objects
});
}];
[task resume];
If you need to cancel this request, just call [task cancel]. So save this NSURLSessionTask in some weak variable if you want to keep track of it and cancel it later.
Don't use the sendAsynchronousRequest convenience API, use the main delegate API so you have access to the instance of NSURLConnecyion to call cancel on.
Also, don't cancel operations on the main queue, you don't know what's there, and consider using NSURLSession
In my project I need to send data to server, for that I've used the following code to achieve the task:
- (void)sendJSONToServer:(NSString *) jsonString
{
// Create a new NSOperationQueue instance.
operationQueue = [NSOperationQueue new];
//
// Create a new NSOperation object using the NSInvocationOperation subclass to run the operationQueueTask method
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(operationQueueTask:)
object:jsonString];
// Add the operation to the queue and let it to be executed.
[operationQueue addOperation:operation];
}//End of sendJSONToServer method
-(void) operationQueueTask:(NSString *) jsonString
{
//NSOperationQueue *remoteResultQueue = [[NSOperationQueue alloc] init];
dispatch_queue_t myQueue = dispatch_queue_create("SERVER_QUEUE",NULL);
dispatch_async(myQueue, ^{
// Performing long running process
// Sending json data to server asynchronously
NSData *postData = [jsonString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:#"%lu", (unsigned long)[jsonString length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"MY_URL_eg_http://www.example.com"]];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
[NSURLConnection sendAsynchronousRequest:request queue:operationQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSLog(#"Response is:%#",[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
}];
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
NSLog(#"Thread Process Finished");
});
});
}//End of operationQueueTask method
By the above code I'm able to send data and get response.
But when there is no internet the data will not be sent to server. How to handle this situation based on the response we get.
Let's say we get response success on fair condition ans false on worst condition.
UPDATED CODE FOR RETRIES
-(id)init
{
self = [super init];
if (self != nil)
{
//initialize stuffs here
pendingOperationQueue = [[NSMutableArray alloc] init];
operationQueue = [NSOperationQueue new];
}
return self;
}//End of init method
- (void)sendJSONToServer:(NSString *) jsonString
{
NSOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(operationQueueTask:) object:[NSString stringWithString:[pendingOperationQueue objectAtIndex:0]]];
[operation start];
}//End of sendJSONToServer method
-(void) operationQueueTask:(NSString *) jsonString
{
//NSOperationQueue *remoteResultQueue = [[NSOperationQueue alloc] init];
dispatch_queue_t myQueue = dispatch_queue_create("SERVER_QUEUE",NULL);
dispatch_async(myQueue, ^{
// Performing long running process
// Sending json data to server asynchronously
NSData *postData = [jsonString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:#"%lu", (unsigned long)[jsonString length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"MY_URL_http://www/example.com"]];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
[NSURLConnection sendAsynchronousRequest:request queue:operationQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSLog(#"Response is:%#",[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
if([[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] rangeOfString:#"true"].location == NSNotFound)
{
// Add the operation to the queue and let it to be executed.
NSLog(#"Failed To Add To Server, Rerunning the task");
}
else
{
NSLog(#"Successfully Added To Server");
NSLog(#"ADDED_DATA_TO_SERVER: %#", jsonString);
if([pendingOperationQueue count] > 0)
{
[pendingOperationQueue removeObjectAtIndex:0];
if([pendingOperationQueue count] > 0)
{
NSOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(operationQueueTask:) object:[NSString stringWithString:[pendingOperationQueue objectAtIndex:0]]];
[operation start];
}
}
}
}];
});
}//End of operationQueueTask method
Heads up! This is a long answer. TL;DR: You can't re-run an NSOperation, but you can design your classes and methods to make it easy to retry requests.
First a quick answer to your title question: you can't re-run an NSOperation, they're not designed to do that. From the docs:
An operation object is a single-shot object — that is, it executes its
task once and cannot be used to execute it again.
With that out of the way, lets take a look at what you're currently doing and clean it up a bit so that re-using it is easier. There's a ton of async stuff going on in there that you don't need; I'll go through it piece by piece.
Let's start with your operationQueueTask: method. The first thing you do in the method is:
dispatch_queue_t myQueue = dispatch_queue_create("SERVER_QUEUE",NULL);
That means that every time that method is called, you're creating a new dispatch queue. While you can do that if you really want to, that's not what dispatch queues are really designed for. A better idea would be to use one of the background queues that are already available:
dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
Next you are dispatching a block asynchronously to that queue. That block:
Sets up your NSMutableURLRequest.
Calls [NSURLConnection sendAsynchronousRequest:...].
Dispatches another block (which has a comment in it about updating the UI) to the main queue.
1 and 2 are fine, I don't see anything you need to change there. 3, however, is problematic because of where that dispatch is being called. The way you have it setup now, NSURLConnection will fire off its asynchronous request and then, before that even has a chance to run, you fire off the block to the main queue to update the UI. What you need to do instead is fire off that block in the completion handler passed to [NSURLConnection sendAsynchronousRequest:...]. Like so:
[NSURLConnection sendAsynchronousRequest:request queue:operationQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSLog(#"Response is:%#",[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
NSLog(#"Thread Process Finished");
});
}];
Now, notice the name of the method you're calling on NSURLConnection? sendAsynchronousRequest:. It actually handles queuing the request on a background queue for you. Which mean, you don't actually need (or want) all the dispatch_* stuff at the beginning of this method. With that in mind, we can reduce it down to:
-(void) operationQueueTask:(NSString *) jsonString
{
NSData *postData = [jsonString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:#"%lu", (unsigned long)[jsonString length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"MY_URL_eg_http://www.example.com"]];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
[NSURLConnection sendAsynchronousRequest:request queue:operationQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSLog(#"Response is:%#",[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
NSLog(#"Thread Process Finished");
});
}];
} //End of operationQueueTask method
Now, on to your sendJSONToServer: method. You're doing a similar thing here that you did at the start of operationQueueTask:: you're creating a new NSOperationQueue each time it runs; that's also not needed (nor typically wanted). What you should probably be doing is creating that operationQueue when your class is initialized (it looks like it's already an instance variable on your class, so you're good there):
// NOTE: I'm just using a default initializer here; if you already have an initializer, use that instead
- (instancetype)init {
if (self = [super init]) {
operationQueue = [NSOperationQueue new];
}
return self;
}
That gets rid of your first line. Next, you're creating an NSInvocationOperation which calls operationQueueTask: and then adding it to your operationQueue. Since you've been re-creating your operationQueue every time, I'm going to assume that it isn't used for anything other than these server requests. In that case, you actually don't need to do this on your operationQueue at all because, as we discovered in the previous method, NSURLConnection is already handling all the background threading for you. In that case, we can actually just copy the code from operationQueueTask: to sendJSONToServer: and get rid of operationQueueTask: altogether. That makes it look like:
- (void)sendJSONToServer:(NSString*)jsonString {
NSData *postData = [jsonString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:#"%lu", (unsigned long)[jsonString length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"MY_URL_eg_http://www.example.com"]];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
[NSURLConnection sendAsynchronousRequest:request queue:operationQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSLog(#"Response is:%#",[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
NSLog(#"Thread Process Finished");
});
}];
}
Note: We still need to keep operationQueue around since we pass it to [NSURLConnection sendAsynchronousRequest:... as the queue that it should run on.
So, how do we go about retrying the request when it fails? The simplest method is to add a recursive function that calls itself when the request fails. You'll pass this method the jsonString you want to send and the maximum number of times it should try to send it before it gives up for good.
To facilitate that, lets make one more change to you existing function: instead of handling the completion block inside the function, lets make the completion block a parameter you pass to the function so that it can be processed elsewhere.
- (void)sendJSONToServer:(NSString*)jsonString withCompletionHandler:(void (^)(NSURLResponse *response, NSData *data, NSError *connectionError))completionHandler {
NSData *postData = [jsonString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:#"%lu", (unsigned long)[jsonString length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"MY_URL_eg_http://www.example.com"]];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
[NSURLConnection sendAsynchronousRequest:request queue:operationQueue completionHandler:completionHandler];
}
Now, lets build that recursive function. I'll call it:
- (void)sendJSONToServer:(NSString*)jsonString withRetryAttempts:(NSUInteger)retryTimes;
The basic flow will be:
Check if retryTimes is greater than 0
If it is, attempt to send the request to the server
When the request finishes, check the response for success
If successful, update the UI on the main queue
If not successful, subtract one from retryTimes and call this function again
That looks something like:
- (void)sendJSONToServer:(NSString*)jsonString withRetryAttempts:(NSUInteger)retryTimes {
if (retryTimes > 0) {
[self sendJSONToServer:jsonString withCompletionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSLog(#"Response is:%#",[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
if (/* check response to make sure it succeeded */) {
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
NSLog(#"Thread Process Finished");
});
} else {
// Note: you can add a dispatch_after here (or something similar) to wait before the next attempt
// You could also add exponential backoff here, which is usually good when retrying network stuff
[self sendJSONToServer:jsonString withRetryAttempts:(retryTimes - 1)];
}
}];
} else {
// We're out of retries; handle appropriately
}
}
Note: There are some bits in there that are just comments because they are application specific; they'll need to be implemented before that code will compile/run.
Now, instead of calling [yourClass sendJSONToServer:jsonString], call: [yourClass sendJSONToServer:jsonString withRetryTimes:maxRetries] and, if the request fails, it should retry up to maxRetries times.
One last note: As #Deftsoft mentioned, Apple's Reachability classes are a nice way to know if you have an active connection to the network or not. It's a good idea to check that first before trying to call sendJSONToServer:withRetryTimes:. That way you're not trying to make requests when it's not possible to even connect in the first place.
You can Apple reachability classes below is the reference code which will provide you the better idea.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(networkChanged:) name:kReachabilityChangedNotification object:nil];
reachability = [Reachability reachabilityForInternetConnection];
[reachability startNotifier];
- (void)networkChanged:(NSNotification *)notification
{
NetworkStatus remoteHostStatus = [reachability currentReachabilityStatus];
if(remoteHostStatus == NotReachable) { NSLog(#"not reachable");}
else if (remoteHostStatus == ReachableViaWiFiNetwork) { NSLog(#"wifi"); }
else if (remoteHostStatus == ReachableViaCarrierDataNetwork) { NSLog(#"carrier"); }
}