I want to change the timeout on my HTTP requests. I'm on a project that uses extensively [NSURLSession sharedSession]. I know that I can't change the configuration of that session (it hasn't one at all).
I know I can define a session with my own config (and I can use as baseline [NSURLSessionConfigurationdefaultSessionConfiguration]), but I don't know how similar is this config to the shared one. The shared one has pre-configured cookie storage policies, cache, etc...
TL;DR I want a session exactly equals to sharedSession, but with a larger timeout. How I can achieve that?
Thanks in advance
One option is to not touch the session, but the requests, using either requestWithURL:cachePolicy:timeoutInterval: or by setting timeoutInterval manually (on an NSMutableURLRequest in the latter case, of course).
Otherwise, you can:
copy the configuration of the sharedSession, (using copy), and modify it
create a new session with this modified configuration, and the same delegate and delegate queue.
Something along the lines of:
NSURLSession *sharedSession = [NSURLSession sharedSession];
NSURLSessionConfiguration *configuration = sharedSession.configuration.copy;
configuration.timeoutIntervalForRequest = whatever;
return [NSURLSession sessionWithConfiguration:configuration delegate:sharedSession.delegate delegateQueue:sharedSession.delegateQueue];
(not tested, but you get the idea).
Of course, you would do this in your own singleton class if you want to reuse the same session.
Sample code for NSURLSessionConfiguration
// Instantiate a session configuration object.
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.timeoutIntervalForRequest = 300;
configuration.HTTPAdditionalHeaders = #{#"Accept" : #"application/xml", #"Content-Type" : #"application/xml; charset=UTF-8", #"User-Agent" : userAgent};
// Instantiate a session object.
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
// Create a data task object to perform the data downloading.
NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:"http://myexample.com"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error != nil) {
// If any error occurs then just display its description on the console.
NSLog(#"Server ERROR: \n "
"CODE: %ld, \n "
"LOCALIZED DESCRIPTION: %# \n "
"DESCRIPTION: %# \n "
"FAILURE REASON: %# \n "
"RECOVERY OPTION: %# \n "
"RECOVERY SUGGESTION: %# \n",
(long)[error code],
[error localizedDescription],
error.description,
error.localizedFailureReason,
error.localizedRecoveryOptions,
error.localizedRecoverySuggestion);
}
else {
// If no error occurs, check the HTTP status code.
NSInteger HTTPStatusCode = [(NSHTTPURLResponse *)response statusCode];
if (HTTPStatusCode == 200) {
//Initialize XML parsing with the response data
/*NSString *str = [[NSString alloc]initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"The URLSession response is: %# \n", str);*/
}
}
}];
// Resume the task.
[task resume];
Related
BACKGROUND I am looping through a bunch of URLs to get several files downloaded. When the files are downloaded I need to 'unpack' the JSON from the files and insert the data into an SQLite database.
PROBLEM When the file is downloaded I attempt to insert the contents of the file into the database and because the files are downloaded asynchronously and the files are different sizes the second file tried to get inserted into the database before the first file has finished and so the database is locked for the subsequent files.
QUESTION How do I get the files to wait for the previous one to be saved to the database before attempting to save the next?
Code to get the files:
-(void)downloadJsonDataFrom:(NSURL *)url withToken:(NSString*)token saveTo:(NSString *)saveLocation withName:(NSString*)fileName
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"GET"];
[request addValue:#"application/json" forHTTPHeaderField:(#"content-type")];
[request addValue:token forHTTPHeaderField:(#"X-TOKEN")];
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:nil delegateQueue:nil];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * data, NSURLResponse * response, NSError * error) {
if (!error && data) {
NSError *writeError = nil;
BOOL writeOK = [data writeToFile:saveLocation options:NSDataWritingAtomic error:&writeError];
if (writeOK) {
NSLog(#"downloadTheFileFrom writeOK for %#", fileName);
[sqlFileHandler saveJsonToSql:saveLocation];
} else {
NSLog(#"Error writing file : %# %#", fileName, writeError);
}
} else {
NSLog(#"downloadTheFileFrom Error : %#",error);
}
}];
[dataTask resume];
Use a serial queue from GCD (Grand Central Dispatch). Some untested code:
dispatch_queue_t serialQueue = dispatch_queue_create("com.unique.sql.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
[sqlFileHandler saveJsonToSql:saveLocation];
});
and something a little swifty-er for those of that persuasion:
let serialQueue = DispatchQueue(label: "com.unique.sql.queue", attr: DISPATCH_QUEUE_SERIAL)
serialQueue.sync {
operationThatNeedsToRunSerially()
}
I created an NSURLSessionConfiguration with some default settings but when I see the request object made with that configuration in my custom NSURLProtocol it doesn't seem that all those settings are inherited and I'm a bit confused.
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSMutableArray *protocolsArray = [NSMutableArray arrayWithArray:config.protocolClasses];
[protocolsArray insertObject:[CustomProtocol class] atIndex:0];
config.protocolClasses = protocolsArray;
// ex. set some random parameters
[config setHTTPAdditionalHeaders:#{#"Authorization":#"1234"}];
[config setAllowsCellularAccess:NO];
[config setRequestCachePolicy:NSURLRequestReturnCacheDataElseLoad];
[config setHTTPShouldSetCookies:NO];
[config setNetworkServiceType:NSURLNetworkServiceTypeVoice];
[config setTimeoutIntervalForRequest:4321];
// Create a request with this configuration and start a task
NSURLSession* session = [NSURLSession sessionWithConfiguration:config];
NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"https://google.com"]];
NSURLSessionDataTask* task = [session dataTaskWithRequest:request];
[task resume];
In my custom NSURLProtocol that is registered
- (void)startLoading {
...
// po [self.request valueForHTTPHeaderField:#"Authorization"] returns 1234
//
// However, I'm very confused why
//
// - allowsCellularAccess
// - cachePolicy
// - HTTPShouldHandleCookies
// - networkServiceType
// - timeoutInterval
//
// for the request return the default values unlike for the header
...
}
Is there some way to check that those parameters I've set are obeyed and inherited by the request?
When dealing with http requests, it is helpful to start with the basics, such as is the request actually being made by the OS, and is a response being received? This will in part help to answer your question about checking that set parameters are infact being obeyed by the request.
I would challenge your use of the word "inherit" in the phrase
Is there some way to check that those parameters I've set are obeyed and inherited by the request?
Inheritance in Object Oriented programming has a very specific meaning. Did you in fact create a custom subclass (let's call it SubClassA) of NSURLRequest with specific properties, and then a further subclass (let's call it SubClassB), and are expecting the second subclass (SubClassB) to inherit properties from its parent (SubClassA)? If so, this is certainly not indicated in the code you provided.
There are several HTTP Proxy programs available which help confirm whether or not the HTTP request is being sent, if a response is received, and also which allow you to inspect the details of the request and the response. Charles HTTP Proxy is one such program. Using Charles, I was able to determine that your code as provided is not making any HTTP request. So you cannot confirm or deny any parameters if the request is not being made.
By commenting out the lines including the CustomProtocol as part of the NSURLSession configuration, and running your code either with or without these lines, I gained some potentially valuable information:
by commenting out the lines including the CustomProtocol, a request was in fact made (and failed), as informed by Charles HTTP Proxy. I also added a completion block to your method dataTaskWithRequest. This completion block is hit when the CustomProtocol configuration lines are commented out. The CustomProtocol's startLoading method is not hit.
when leaving in the original lines to configure the NSURLSession using the CustomProtocol, there was no request recorded by Charles HTTP Proxy, and the completion handler is not hit. However, the CustomProtocol's startLoading method is hit.
Please see code below (modifications made to the code posted in the original question).
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSMutableArray *protocolsArray = [NSMutableArray arrayWithArray:config.protocolClasses];
//[protocolsArray insertObject:[CustomProtocol class] atIndex:0];
//config.protocolClasses = protocolsArray;
// ex. set some random parameters
[config setHTTPAdditionalHeaders:#{#"Authorization":#"1234"}];
[config setAllowsCellularAccess:NO];
[config setRequestCachePolicy:NSURLRequestReturnCacheDataElseLoad];
[config setHTTPShouldSetCookies:NO];
[config setNetworkServiceType:NSURLNetworkServiceTypeVoice];
[config setTimeoutIntervalForRequest:4321];
// Create a request with this configuration and start a task
NSURLSession* session = [NSURLSession sessionWithConfiguration:config];
NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"https://google.com"]];
NSURLSessionDataTask* task = [session dataTaskWithRequest:request
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSString * auth = [request valueForHTTPHeaderField:#"Authorization"];
NSLog(#"Authorization: %#", auth);
BOOL allowsCellular = [request allowsCellularAccess];
NSString * allowsCellularString = allowsCellular ? #"YES" : #"NO";
NSLog(#"Allows cellular: %#", allowsCellularString);
}];
[task resume];
This gives you the information that the CustomProtocol is not properly handling the request. Yes, the breakpoint inside the startLoading method is hit when the CustomProtocol is configured as part of the NSURLSession, but that is not definitive proof that the CustomProtocol is handling the request properly. There are many steps necessary to using a CustomProtocol, as outlined by Apple (Protocol Support, NSURLProtocol Class Reference) that you should confirm you are following.
Some things to make sure are working:
if you are using a CustomProtocol, that means you are likely trying to handle a different protocol other than http, https, ftp, ftps, etc.
make sure that your end point (the server which is listening for the http requests and responding) can actually accept the request and reply.
if you are setting an HTTP Authorization Header, make sure that the server can respond appropriately, and that the credentials are valid if you are expecting a positive response
remember to register your CustomProtocol
for example:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[NSURLProtocol registerClass:[CustomProtocol class]];
return YES;
}
Below is a unit tests to verify that the NSURLSession is functioning as expected (without using our custom protocol explicitly). Note that this unit test does pass when added to Apple's own sample code for the project CustomHTTPProtocol, but does not pass using our very bare bones CustomProtocol
- (void)testNSURLSession {
XCTestExpectation *expectation = [self expectationWithDescription:#"Testing standard NSURL Session"];
[[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:#"https://www.apple.com/"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
#pragma unused(data)
XCTAssertNil(error, #"NSURLSession test failed with error: %#", error);
if (error == nil) {
NSLog(#"success:%zd / %#", (ssize_t) [(NSHTTPURLResponse *) response statusCode], [response URL]);
[expectation fulfill];
}
}] resume];
[self waitForExpectationsWithTimeout:3.0 handler:^(NSError * _Nullable error) {
if(nil != error) {
XCTFail(#"NSURLSession test failed with error: %#", error);
}
}];
}
Below is a unit test which may be used to verify that the configurations made to a NSURLSession are as expected, when configuring using our own CustomProtocol class. Again, please note that this test fails using the empty implementation of CustomProtocol but this is expected if using Test Driven Development (create the test first, and then the code second which will allow the test to pass).
- (void)testCustomProtocol {
XCTestExpectation *expectation = [self expectationWithDescription:#"Testing Custom Protocol"];
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSMutableArray *protocolsArray = [NSMutableArray arrayWithArray:config.protocolClasses];
[protocolsArray insertObject:[CustomProtocol class] atIndex:0];
config.protocolClasses = protocolsArray;
// ex. set some random parameters
[config setHTTPAdditionalHeaders:#{#"Authorization":#"1234"}];
[config setAllowsCellularAccess:NO];
[config setRequestCachePolicy:NSURLRequestReturnCacheDataElseLoad];
[config setHTTPShouldSetCookies:NO];
[config setNetworkServiceType:NSURLNetworkServiceTypeVoice];
[config setTimeoutIntervalForRequest:4321];
// Create a request with this configuration and start a task
NSURLSession* session = [NSURLSession sessionWithConfiguration:config];
NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"https://www.apple.com"]];
NSURLSessionDataTask* task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
#pragma unused(data)
XCTAssertNil(error, #"test failed: %#", error.description);
if (error == nil) {
NSLog(#"success:%zd / %#", (ssize_t) [(NSHTTPURLResponse *) response statusCode], [response URL]);
NSString * auth = [request valueForHTTPHeaderField:#"Authorization"];
NSLog(#"Authorization: %#", auth);
XCTAssertNotNil(auth);
BOOL allowsCellular = [request allowsCellularAccess];
XCTAssertTrue(allowsCellular);
XCTAssertEqual([request cachePolicy], NSURLRequestReturnCacheDataElseLoad);
BOOL shouldSetCookies = [request HTTPShouldHandleCookies];
XCTAssertTrue(shouldSetCookies);
XCTAssertEqual([request networkServiceType], NSURLNetworkServiceTypeVoice);
NSTimeInterval timeOutInterval = [request timeoutInterval];
XCTAssertEqualWithAccuracy(timeOutInterval, 4321, 0.01);
[expectation fulfill];
}
}];
[task resume];
[self waitForExpectationsWithTimeout:3.0 handler:^(NSError * _Nullable error) {
if(nil != error) {
XCTFail(#"Custom Protocol test failed with error: %#", error);
}
}];
}
I need to use NSURLSession to make network calls. On the basis of certain things, after I receive the response, I need to return an NSError object.
I am using semaphores to make the asynchronous call behave synchronously.
The problem is, the err is set properly inside call, but as soon as semaphore ends (after
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
), the err becomes nil.
Please help
Code:
-(NSError*)loginWithEmail:(NSString*)email Password:(NSString*)password
{
NSError __block *err = NULL;
// preparing the URL of login
NSURL *Url = [NSURL URLWithString:urlString];
NSData *PostData = [Post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
// preparing the request object
NSMutableURLRequest *Request = [[NSMutableURLRequest alloc] init];
[Request setURL:Url];
[Request setHTTPMethod:#"POST"];
[Request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[Request setHTTPBody:PostData];
NSMutableDictionary __block *parsedData = NULL; // holds the data after it is parsed
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.TLSMinimumSupportedProtocol = kTLSProtocol11;
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil];
NSURLSessionDataTask *task = [session dataTaskWithRequest:Request completionHandler:^(NSData *data, NSURLResponse *response1, NSError *err){
if(!data)
{
err = [NSError errorWithDomain:#"Connection Timeout" code:200 userInfo:nil];
}
else
{
NSString *formattedData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#", formattedData);
if([formattedData rangeOfString:#"<!DOCTYPE"].location != NSNotFound || [formattedData rangeOfString:#"<html"].location != NSNotFound)
{
loginSuccessful = NO;
//*errorr = [NSError errorWithDomain:#"Server Issue" code:201 userInfo:nil];
err = [NSError errorWithDomain:#"Server Issue" code:201 userInfo:nil];
}
else
{
parsedData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&err];
NSMutableDictionary *dict = [parsedData objectForKey:#"User"];
loginSuccessful = YES;
}
dispatch_semaphore_signal(semaphore);
}];
[task resume];
// but have the thread wait until the task is done
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return err;
}
Rob's answer tells you how to do it right, but not what mistake you made:
You have two variables named err, which are totally unrelated. It seems that you haven't turned on some important warnings, otherwise your code wouldn't even have compiled.
The parameter err that is passed to your completion block is the error from the URL request. You replace it without thinking with a timeout error - so the true error is now lost. Consider that timeout is not the only error.
But all the errors that you set only set the local variable err which was passed to you in the completion block; they never touch the variable err in the caller at all.
PS. Several serious errors in your JSON handling. JSON can come in UTF-16 or UTF-32, in which case formattedData will be nil and you incorrectly print "Server Issue". If the data isn't JSON there is no guarantee that it contains DOCTYPE or html, that test is absolute rubbish. Your user with the nickname JoeSmith will hate you.
Passing NSJSONReadingAllowFragments to NSJSONSerialization is nonsense. dict is not mutable; if you try to modify it your app will crash. You don't check that the parser returned a dictionary, you don't check that there is a value for the key "User", and you don't check that the value is a dictionary. That's lots of ways how your app can crash.
I would suggest cutting the Gordian knot: You should not use semaphores to make an asynchronous method behave synchronously. Adopt asynchronous patterns, e.g. use a completion handler:
- (void)loginWithEmail:(NSString *)email password:(NSString*)password completionHandler:(void (^ __nonnull)(NSDictionary *userDictionary, NSError *error))completionHandler
{
NSString *post = ...; // build your `post` here, making sure to percent-escape userid and password if this is x-www-form-urlencoded request
NSURL *url = [NSURL URLWithString:urlString];
NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
// [request setValue:postLength forHTTPHeaderField:#"Content-Length"]; // not needed to set length ... this is done for you
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"]; // but it is best practice to set the `Content-Type`; use whatever `Content-Type` appropriate for your request
[request setValue:#"text/json" forHTTPHeaderField:#"Accept"]; // and it's also best practice to also inform server of what sort of response you'll accept
[request setHTTPBody:postData];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.TLSMinimumSupportedProtocol = kTLSProtocol11;
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *err) {
if (!data) {
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(nil, [NSError errorWithDomain:#"Connection Timeout" code:200 userInfo:nil]);
});
} else {
NSError *parseError;
NSDictionary *parsedData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&parseError];
dispatch_async(dispatch_get_main_queue(), ^{
if (parsedData) {
NSDictionary *dict = parsedData[#"User"];
completionHandler(dict, nil);
} else {
completionHandler(nil, [NSError errorWithDomain:#"Server Issue" code:201 userInfo:nil]);
}
});
}
}];
[task resume];
}
And then call it like so:
[self loginWithEmail:userid password:password completionHandler:^(NSDictionary *userDictionary, NSError *error) {
if (error) {
// do whatever you want on error here
} else {
// successful, use `userDictionary` here
}
}];
// but don't do anything reliant on successful login here; put it inside the block above
Note:
I know you're going to object to restoring this back to asynchronous method, but it's a really bad idea to make this synchronous. First it's a horrible UX (the app will freeze and the user won't know if it's really doing something or whether it's dead) and if you're on a slow network you can have all sorts of problems (e.g. the watchdog process can kill your app if you do this at the wrong time).
So, keep this asynchronous. Ideally, show UIActivityIndicatorView before starting asynchronous login, and turn it off in the completionHandler. The completionHandler would also initiate the next step in the process (e.g. performSegueWithIdentifier).
I don't bother testing for HTML content; it is easier to just attempt parse JSON and see if it succeeds or not. You'll also capture a broader array of errors this way.
Personally, I wouldn't return my own error objects. I'd just go ahead and return the error objects the OS gave to me. That way, if the caller had to differentiate between different error codes (e.g. no connection vs server error), you could.
And if you use your own error codes, I'd suggest not varying the domain. The domain should cover a whole category of errors (e.g. perhaps one custom domain for all of your app's own internal errors), not vary from one error to another. It's not good practice to use the domain field for something like error messages. If you want something more descriptive in your NSError object, put the text of the error message inside the userInfo dictionary.
I might suggest method/variable names to conform to Cocoa naming conventions (e.g. classes start with uppercase letter, variables and method names and parameters start with lowercase letter).
There's no need to set Content-Length (that's done for you), but it is good practice to set Content-Type and Accept (though not necessary).
You need to let the compiler know that you will be modifying err. It needs some special handling to preserve that beyond the life of the block. Declare it with __block:
__block NSError *err = NULL;
See Blocks and Variables in Blocks Programming Topics for more details.
I'm pretty new with security topic and I have question about it. I create application under iOS. I need to connect with some server via HTTP and then get its public key (SSL certificate). Then I need to use this public key to encrypt some of data and send them to the same server. My problem is I have no idea how to do that. Can anyone explain to me how can I obtain the public key under iOS and then use it to encrypt data?
As an example, when you use a https-prefixed url (https==http over ssl), NSURLSession will handle this for you. A simple GET will look like the code below.All the handshake, public key stuff will be handled for you.
If you use post and send data to the server, the encryption will also be handled for you if a https url is used.
// use default session configuration
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
// create session without a delegate, use global operation queue
NSURLSession *session = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
// create a data task printing the response payload to NSLog
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:#"https://developer.apple.com"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Response: %#", responseString);
}];
// run
[dataTask resume];
In my app i need to call two services at a time. for single service i am using the below code:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Instantiate a session object.
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSURL *url = [NSURL URLWithString:#"my link"];
// Create a data task object to perform the data downloading.
NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error != nil) {
// If any error occurs then just display its description on the console.
NSLog(#"%#", [error localizedDescription]);
}
else{
// If no error occurs, check the HTTP status code.
NSInteger HTTPStatusCode = [(NSHTTPURLResponse *)response statusCode];
// If it's other than 200, then show it on the console.
if (HTTPStatusCode != 200) {
NSLog(#"HTTP status code = %d", (int)HTTPStatusCode);
} else {
NSMutableArray *jsonData = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves error:nil];
NSLog(#"json data ==========> %#", jsonData);
}
}
}];
// Resume the task.
[task resume];
by using this i am getting the data. Now, at the same time i need to call another service. How can i achieve this? and How i will get the data?