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.
Related
I want to integrate digest authentication in my objective c code.
I am using NSURLSessionDataTask.
In first webservice call i fetch the nonce value and then in next call i set the header of the same webservice.
NSDictionary *payload;
payload = #{
#"Username" : #"",
#"Password" : #"",
};
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#""]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0];
[request setHTTPMethod:#"GET"];
NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:payload
options:0 error:&error];
request.HTTPBody = data;
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(#"%#", error);
} else {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
NSLog(#"%#", httpResponse);
NSDictionary* dictHeaders = [(NSHTTPURLResponse *)response allHeaderFields];
NSLog(#"%#",[dictHeaders objectForKey:#"Www-Authenticate"]);
[self callWSAfterAuthentication:[dictHeaders objectForKey:#"Www-Authenticate"]];
}
}];
[dataTask resume];
You have not said in your question what exact problems you have. Assuming the authentication simply does not work:
Add a NSURLSessionDelegate to your NSURLSession and handle the authentication in the URLSession:didReceiveChallenge:completionHandler: delegate method. Check for NSURLAuthenticationMethodHTTPDigest and respond accordingly. (You can find info on how to implement this)
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) {
}];
}
}];
I am using NSURLSessionDataTask and NSMutableURLRequest. I noticed that the NSMutableURLRequest has a timeout (240 seconds I believe, which is a long time). I also read that NSURLSession has a timeout also but I am unsure of exactly what it is. My question is, will the app crash if I do not handle a timeout if it occurs? Is it necessary to handle timeouts or does the OS handle it and prevents the app from crashing, and just kills the request. If we must handle it then it would be great to get some feedback in regards to my code example;
NSURLSession * session = [NSURLSession sharedSession];
NSURL * url = [[NSURL alloc] initWithString:self.url];
NSMutableURLRequest * request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:#"POST"];
[request addValue:#"application/x-www-form-urlencoded; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
NSString * params =[NSString stringWithFormat:#"email=%#",some email];
[request setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask * task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse * httpResp = (NSHTTPURLResponse *)response;
NSDictionary * dataDictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
//some code missing
}
[task resume];
dataTaskWithRequest:completionHandler: has an error parameter. If a timeout occurs, you will get a non-nil error object passed in to the completion block, and you should handle that however is appropriate for your app. You need to be handling that anyway for other types of errors that may occur. The documentation on this method is pretty sparse, but I presume that you will get a nil data object if the download fails (for any reason including a timeout), so you should check for that before you try to do anything with the data.
NSURLSessionDataTask * task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse * httpResp = (NSHTTPURLResponse *)response;
if (data) {
NSDictionary * dataDictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
}else{
NSLog (#"%#", error);
// do whatever to handle the error;
}
I'm facing a very strange issue when using NSURLSessionDataTask to post a JSON request to the server.
The first request goes through and I receive the correct JSON response, when I do a second request I'm getting always back the old response and the server never receives the request. Even if I turn on airplane mode the NSURLSessionDataTask does work an I get back the old response again.
That's the code I'm using:
- (void)getJSONFromURL:(NSURL*)url identifierCode:(NSInteger)code
{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request addValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request addValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request addValue:[DataController sharedInstance].currentUser.userToken forHTTPHeaderField:#"User-Token"];
[request setHTTPMethod:#"GET"];
if (![SVProgressHUD isVisible])
[SVProgressHUD show];
NSURLSessionDataTask *postTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
[self handleResponse:httpResponse withJSON:json identifierCode:code];
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss];
});
}];
[postTask resume];
}
I've found the issue, I don't know why this has changed, because I never had to set that property before. Anyway I set configuration.URLCache = NULL; and now everthing works fine again.
you can set
request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
to disable it per request
Undoubtedly, people will shake their head in shame at the following, but I would appreciate the help.
I seem to make the same calls to NSURLSessionUploadTask with pretty much the same structure throughout the program. Hence, I am trying to make it a method which I can call from anywhere in the program and, when it's complete, return the NSDictionary to the call which made it.
Currently I have the following:
-(NSDictionary *)serverRequest:(NSString *)requestURL withDictionary:(NSDictionary *)sendDict {
NSURL *homeURL = [[NSURL alloc] initWithString:[NSString stringWithFormat:#"%#/%#",baseURL, requestURL]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:homeURL];
[request setHTTPMethod:#"POST"];
[request addValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request addValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setCachePolicy:NSURLCacheStorageAllowedInMemoryOnly];
NSData *sentData = [NSJSONSerialization dataWithJSONObject:sendDict options:0 error:nil];
NSURLSessionUploadTask *uploadTask = [_session uploadTaskWithRequest:request fromData:sentData completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
returnedData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
if ([returnedData[#"code"] isEqualToString:#"OK"])
{
dispatch_async(dispatch_get_main_queue(), ^{
return returnedData;
});
}
}];
[uploadTask resume];
}
So I pass in the URL I want to call, and the NSDictionary which is then sent to the server. The server replies, with a JSON response, which I then turn into returnedData. I simply wish to pass this data back to the call which made it ... something like:
NSDictionary *mydata = #{#"email": userRegisterTextFieldEmailAddress.text};
NSDictionary *passedBackData = [self serverRequest:#"checkUserName" withDictionary:mydata];
When I try this it won't build as the dispatch_async isn't the right format etc, etc. I have tried to read up on this, and can't believe it's a hard problem? Surely others aren't making the same call with the same code each time they want to return some data from a web server?
Thanks in advance for any help you can give in clearing up my misunderstanding.
What you'd generally do is add a completion block parameter to your method, that specifies what you want to do upon receiving the response. So, add a block parameter to your method and add the code that calls that block, something like:
- (void) serverRequest:(NSString *)requestURL withDictionary:(NSDictionary *)sendDict completion:(void (^)(id responseObject, NSError *error))completion
{
NSURL *homeURL = [[NSURL alloc] initWithString:[NSString stringWithFormat:#"%#/%#", baseURL, requestURL]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:homeURL];
[request setHTTPMethod:#"POST"];
[request addValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request addValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setCachePolicy:NSURLCacheStorageAllowedInMemoryOnly];
NSData *sentData = [NSJSONSerialization dataWithJSONObject:sendDict options:0 error:nil];
NSURLSessionUploadTask *uploadTask = [_session uploadTaskWithRequest:request fromData:sentData completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// report any network-related errors
if (!data) {
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(nil, error);
});
}
return;
}
// report any errors parsing the JSON
NSError *parseError = nil;
returnedData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
if (!returnedData) {
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(nil, parseError);
});
}
return;
}
// if everything is ok, then just return the JSON object
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(returnedData, nil);
});
}
}];
[uploadTask resume];
}
You'd then call this serverRequest method, specifying precisely what you want to do with the data you receive:
[self serverRequest:url withDictionary:dictionary completion:^(id responseObject, NSError *error) {
if (responseObject) {
// do what you want with the response object here
} else {
NSLog(#"%s: serverRequest error: %#", __FUNCTION__, error);
}
}];
Clearly, you can change this as you see fit, but hopefully this illustrates the idea. If, for example, you knew that responseObject was always going to be a NSDictionary, then I'd change the references to id responseObject with NSDictionary *responseObject.
But the idea is that you should just provide a block that takes the response data and/or the error as parameters. That way, the code that calls this method can specify what should happen when the asynchronous network request completes.
So the call is asynch so you can't really do it like that. What I tend to do is wait for the method to return on its own and then get the main thread (as you've done) and then update UI/models etc like that.