Downloading files in serial order using NSURLConnection in iOS - ios

I want to download 3 files in serial order. Two of them are txt files and one is .gz file. I am using NSURLConnection to download the above files.
I am very new to iOS Programming. I have seen in other question in SO and google that we can use serial dispatch queue to do some operation serially.
But I don't know how to do this with NSURLConnection. I tried below but did not work.
dispatch_queue_t serialQueue = dispatch_queue_create("com.clc.PropQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
[self downloadProp];
});
dispatch_async(serialQueue, ^{
[self downloadDatabase];
});
dispatch_async(serialQueue, ^{
[self downloadTxt];
});
Above code is not executing connectionDidFinishLoading of NSURLCOnnection. Anyone has Idea how to achieve this?

NSURLSession provides a queue that will download each task in the order in which they are created.
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionTask *task1 = [session dataTaskWithURL:[NSURL URLWithString:#"http://yahoo.com"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"Complete 1");
}];
NSURLSessionTask *task2 = [session dataTaskWithURL:[NSURL URLWithString:#"http://msn.com"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"Complete 2");
}];
NSURLSessionTask *task3 = [session dataTaskWithURL:[NSURL URLWithString:#"http://google.com"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"Complete 3");
}];
// Regardless of which order the tasks are "resumed" (aka started) they will execute synchronously in the order added, above.
[task3 resume];
[task1 resume];
[task2 resume];
Update based on comments & chat:
To be more deterministic over the ordering & execution of tasks...
NSURLSession *session = [NSURLSession sharedSession];
__block NSURLSessionTask *task1 = nil;
__block NSURLSessionTask *task2 = nil;
__block NSURLSessionTask *task3 = nil;
task1 = [session dataTaskWithURL:urlToFirstFile completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// CHECK ERROR
NSLog(#"First file completed downloading");
[task2 resume];
}];
task2 = [session dataTaskWithURL:urlToSecondFile completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// CHECK ERROR
NSLog(#"Second file completed downloading");
[task3 resume];
}];
task3 = [session dataTaskWithURL:[NSURL URLWithString:#"http://google.com"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// CHECK ERROR
NSLog(#"Third file completed downloading");
}];
[task1 resume];

A simple recursive solution to ensure serial operation.
func serialisedRequests(session: URLSession, requests: [URLRequest], index: Int = 0) {
if index >= requests.count {
return
}
let task = session.dataTask(with: requests[index]) {
data, response, error in
serialisedRequests(session: session, requests: requests, index: index+1)
}
task.resume()
}

Simply set your NSURLSession's HTTPMaximumConnectionsPerHost property to 1 and add the tasks in the order you want.
See this answer for more details:
https://stackoverflow.com/a/21018964

Related

iOS: Making multiple NSURLSession request

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

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.

Does NSURLSession for HTTP data task (NSURLSessionDataTask) runs in background thread or we will have to provide the queue?

I started to use NSURLSession by avoiding NSURLConnection now a days as it's a new and elegant API provided by Apple. Previously, I used to put call NSURLRequest in GCD block to execute it in background. Here is how I used to do in past:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:#"www.stackoverflow.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLResponse *response;
NSError *error;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error) {
// Handle error
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
// Do something with the data
});
});
Now, here is how I use NSURLSession:
- (void)viewDidLoad
{
[super viewDidLoad];
/*-----------------*
NSURLSession
*-----------------*/
NSURL *url = [NSURL URLWithString:#"https://itunes.apple.com/search?term=apple&media=software"];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
NSLog(#"%#", json);
}];
}
I want to know that, will my request be executed on background thread itself or I will have to provide my own mechanism same way I did in case of NSURLRequest ?
No, you don't need to use GCD to dispatch this to background queue. In fact, because the completion block runs on background thread, the exact opposite is true, that if you need anything in that block to run on the main queue (e.g., synchronized updates to model objects, UI updates, etc.), you have to manually dispatch that to the main queue yourself. For example, let's imagine that you were going to retrieve a list of results and update the UI to reflect this, you might see something like:
- (void)viewDidLoad
{
[super viewDidLoad];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:#"https://itunes.apple.com/search?term=apple&media=software"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// this runs on background thread
NSError *error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
// detect and handle errors here
// otherwise proceed with updating model and UI
dispatch_async(dispatch_get_main_queue(), ^{
self.searchResults = json[#"results"]; // update model objects on main thread
[self.tableView reloadData]; // also update UI on main thread
});
NSLog(#"%#", json);
}];
[dataTask resume];
}

method returns before completionhandler

I am developing a networkUtil for my project, I need a method that gets a url and returns the JSON received from that url using NSURLSessionDataTask to get a JSON from server. the method is the following:
+ (NSDictionary*) getJsonDataFromURL:(NSString *)urlString{
__block NSDictionary* jsonResponse;
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", jsonResponse);
}];
[dataTask resume];
return jsonResponse;
}
The problem is that the completionHandler inside my method and the method itself are run on different threads and in the last line the jsonResponse is always nil
How should I set jsonResponse with returned json from urlString?
What is the best practice for this issue?
Block that is running in NSURLSession is running on different thread - your method doesn't wait block to finish.
You have two options here
First one. Send NSNotification
+ (void) getJsonDataFromURL:(NSString *)urlString{
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary* jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", jsonResponse);
[[NSNotificationCenter defaultCenter] postNotificationName:#"JSONResponse" object:nil userInfo:#{#"response" : jsonResponse}];
}];
[dataTask resume];
}
Second one. Past completion block to this utility method
+ (void) getJsonDataFromURL:(NSString *)urlString
completionBlock:(void(^)(NSDictionary* response))completion {
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary* jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", jsonResponse);
completion(jsonResponse);
}];
[dataTask resume];
}
Some people could say it is a horrible advice but you also can download your data synchronously. It should be done in a background queue. It is not a best practice but for some cases (like a command line utility, non-critical background queue) it is ok.
NSURLSession does not have synchronous download method but you can easily bypass it with semaphore:
+ (NSDictionary*) getJsonDataFromURL:(NSString *)urlString{
__block NSDictionary* jsonResponse;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); // Line 1
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", jsonResponse);
dispatch_semaphore_signal(semaphore); // Line 2
}];
[dataTask resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // Line 3
return jsonResponse;
}
NSURLSession has a delegateQueue property which is used for "delegate method calls and completion handlers related to the session". By default NSURLSession always creates a new delegateQueue during initialisation. But if you set a NSURLSession's delegation queue yourself make sure you do not call your method in the same queue since it will block it.
It is obviously Method will return before Block is completed. Because that is main purpose of the block.
You needs to change something like this :
NSDictionary* jsonResponse;
+ (NSDictionary*) getJsonDataFromURL:(NSString *)urlString{
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
self.jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", jsonResponse);
[dataTask resume];
// ad observer here that call method to update your UI
}];
}
This is the intended behavior of "asynchronous calls". They shall not block the calling thread, but execute the passed block, when the call is done.
Simply add the code that has to be executed after getting the result in the block.
So instead of …
+ (NSDictionary*) getJsonDataFromURL:(NSString *)urlString
{
__block NSDictionary* jsonResponse;
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error)
{
jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", jsonResponse);
}];
[dataTask resume];
return jsonResponse;
}
…
NSDictionary* jsonResponde = [self getJsonDataFromURL:url]; // BTW: get infringes the naming rules of Objective-C
// The code that has to be executed is here
… you do this:
+ (void) getJsonDataFromURL:(NSString *)urlString
{
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error)
{
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", jsonResponse);
// Place the code to be executed here <-----
}];
[dataTask resume];
}
…
[self getJsonDataFromURL:url]; // BTW: get infringes the naming rules of Objective-C
// No code here any more
If the code executed by -getJsonDataFromURL: depends on the caller, simply pass it as an argument to the method and execute it at the specified location. If you need help for that, let me know. I will add the code for it.
Another solution is to use a semaphore and wait, until the completion handler is executed. But this will block the UI and is the not-intended way to do it.

How to return NSData from NSURLSessionDataTask completion handler

I am trying to make a simple class that I can use to call a post web service.
Everything is working perfectly except that I am not able to return the NSData.
This is my code:
+ (NSData *)postCall:(NSDictionary *)parameters fromURL:(NSString *)url{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSMutableArray *pairs = [[NSMutableArray alloc]init];
for(NSString *key in parameters){
[pairs addObject:[NSString stringWithFormat:#"%#=%#", key, parameters[key]]];
}
NSString *requestParameters = [pairs componentsJoinedByString:#"$"];
NSURL *nsurl = [NSURL URLWithString:url];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:nsurl];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:[requestParameters dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//return data;
}];
[dataTask resume];
return nil;
}
Please notice that I have //return data but it gives me this error
Incompatible block pointer types sending 'NSData *(^)(NSData *__strong, NSURLResponse *__strong, NSError *__strong)' to parameter of type 'void (^)(NSData *__strong, NSURLResponse *__strong, NSError *__strong)'
My question is:
Is my way good or it will cause me problems in the future? I don't have image to download and I don't have anything to upload, I just have to send simple string data and receive simpe string data. Or it will be better to but that code in each function independently?
How can I return the data please?
You cannot just return the data (because the NSURLSessionDataTask runs asynchronously). You probably want to employ your own completion block pattern, similar to the completionHandler of the dataTaskWithRequest method.
So, you would add your own block parameter to your method, that you'll invoke from inside the dataTaskWithRequest method's completionHandler:
+ (NSURLSessionDataTask *)postCall:(NSDictionary *)parameters fromURL:(NSString *)url completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler {
// create your request here ...
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (completionHandler)
completionHandler(data, response, error);
}];
[dataTask resume];
return dataTask;
}
Or, because this dataTaskWithRequest runs on a background thread, it’s sometimes useful to make sure to dispatch the completion handler back to the main queue, e.g.
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (completionHandler)
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(data, response, error);
});
}];
Note, as an aside, I think it's good to return the NSURLSessionDataTask reference, like above, so (a) the caller can make sure the data task was successfully created; and (b) you have the NSURLSessionTask reference that you can use to cancel the task in case, at some future date, you want to be able to cancel the request for some reason (e.g. the user dismisses the view controller from which the request was issued).
Anyway, you'd then invoke this with:
NSURLSessionTask *task = [MyClass postCall:parameters fromURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// put whatever code you want to perform when the asynchronous data task completes
}];
if (!task) {
// handle failure to create task any way you want
}
You ask:
Is my way good or it will cause me problems in the future? I don't have [an] image to download and I don't have anything to upload, I just have to send [some] simple string data and receive [simple] string data. Or it will be better to but that code in each function independently?
If you're receiving simple string data back, I'd suggest composing your response in JSON format, and then having the completion block in postCall use NSJSONSerialization to extract the response. Using JSON makes it easier for the app to differentiate between successful response and a variety of server related problems that might also return string responses.
So, let's say you modified your server code to return a response like so:
{"response":"some text"}
Then you could modify postCall to parse that response like so:
+ (NSURLSessionDataTask *)postCall:(NSDictionary *)parameters fromURL:(NSString *)url completionHandler:(void (^)(NSString *responseString, NSError *error))completionHandler {
// create your request here ...
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (completionHandler) {
if (error) {
completionHandler(nil, error);
} else {
NSError *parseError = nil;
NSDictionary *responseDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
completionHandler(responseDictionary[#"response"], parseError);
}
}
}];
[dataTask resume];
return dataTask;
}
In terms of your underlying question, whether a method like postCall makes sense, yes, I think it makes perfect sense to put the details of creating the request in a single method. My minor reservation in your implementation was your decision to make it a class method rather than an instance method. You're currently creating a new NSURLSession for each request. I'd suggest making postCall an instance method (of a singleton if you want) and then saving the session as a class property, which you set once and then re-use on subsequent queries.
You should use a block method.
First define a block
typedef void (^OnComplete) (NSData *data);
Use the following method
+ (void)postCall:(NSDictionary *)parameters fromURL:(NSString *)url withBlock:(OnComplete)block; {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSMutableArray *pairs = [[NSMutableArray alloc]init];
for(NSString *key in parameters){
[pairs addObject:[NSString stringWithFormat:#"%#=%#", key, parameters[key]]];
}
NSString *requestParameters = [pairs componentsJoinedByString:#"&"];
NSURL *myURL = [NSURL URLWithString:url];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:myURL];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:[requestParameters dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
block(data);
}];
[dataTask resume];
}

Resources