Downloading muliple files in background thread? - ios

I was downloading files at a time or one by one or one only according to user requirements. After downloading files I am sending notification to another view as sucessfull message.
When I download a single file at a time it was successfully downloading the file. But when I was trying to download two or more files within time gap of 6 sec (for pressing another download button), first files are not downloading. It downloads only last file which I have send to download.
Any help would be appreciated.
url=[NSURL URLWithString:currentURL];
NSMutableURLRequest * request = [[NSMutableURLRequest alloc]initWithURL:url];
[request setHTTPMethod:#"GET"];
NSURLConnection *connection=[[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:YES];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
{ //Background Thread
{
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *dataMain, NSError *error)
{
if ([dataMain length]/1024.0f > 600 && error == nil)
{
[dataMain writeToFile:pathOriginal atomically:YES];
NSLog(#"orginal file saved");
}
}];
}
dispatch_async(dispatch_get_main_queue(), ^(void){
[[NSNotificationCenter defaultCenter] postNotificationName:#"TestNotification" object:self];
}); });

Use dispatch async before each call. That way each call will run on a different thread, and will solve your issue.
Hope this helps!

NSURLConnection is deprecated. You should be using NSURLSession for new development. An NSURLSession will handle multiple downloads. (So would NSURLConnection, but it's not worth debugging that given that it's deprecated.

Related

NSURLConnection send request after finish all process

I have a nested loop of sending the request.
-(void) download
{
for(NSString *id in array)
{
//init with request and start the connection
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy: NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request deletegate:self];
[conn start];
}
}
-(void) connection:(NSURLConnection *) connection didReceiveData:(NSData *) data
{
//enter here secondly
}
-(void) connectionDidFinishLoading:(NSURLConnection *) connection
{
//enter here last, after finish the for loop
//my intention is use the downloaded data to do something before sending a new request.
}
The problem is that I want to enter "-(void) connectionDidFinishLoading:(NSURLConnection *) connection" first before send the request again in the for loop.
But currently it will finish the for loop and sent all the request before enter to "-(void) connectionDidFinishLoading:(NSURLConnection *) connection".
You Should Try This NSURLConnection is deprecated in iOS9
for (NSString *URL in URLArray) {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// check error and/or handle response here
}];
[task resume];
}
and use dispatch_group_t group = dispatch_group_create();
add line to for loop dispatch_group_enter(group); will call
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// Request Finish
});
for your goal
In your case you need to try block function because as per your requirement you want response of the first connection for another request.
for(NSString* url in array)
{
// Generate a NSURLRequest object from the address of the API.
NSURL *url = [NSURL URLWithString:urlLink];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// Send the request asynchronous request using block!
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
NSLog(#"Error in updateInfoFromServer: %# %#", error, [error localizedDescription]);
} else if (!response) {
NSLog(#"Could not reach server!");
} else if (!data) {
NSLog(#"Server did not return any data!");
} else {
[self doStuffWithData:data];
}
}];
}
URL loading is not a synchronous operation (or at least should never be done synchronously), because it can take up to 90 seconds just for a DNS lookup failure, and almost infinitely long if the server keeps dribbling out data. If you block the main thread for even a fraction of that amount of time, iOS will kill your app.
Instead of scheduling the requests in a loop and waiting for them to finish, you need to schedule the first request (and only the first request). Then, in your connectionDidFinishLoading: method (and maybe your connection:DidFailWithError: method), schedule the next request.
With that said, unless you still need to support iOS 6/10.8 and earlier, you should probably be using NSURLSession. (The same general advice applies; the delegate method names are changed to protect the guilty.)

IOS/Objective-C: Send Synchronous request for login

Hi I have been sending a login with asynchronous request (as it is commonly advised to use asynchronous where possible) but I now want to make it synchronous to better control when I receive the response.
Can someone suggest how to alter the asynchronous code below to synchronous?
Thanks for any suggestions:
NSURL *url = [NSURL URLWithString:#"http://~/login.php"];
NSMutableURLRequest *rq = [NSMutableURLRequest requestWithURL:url];
[rq setHTTPMethod:#"POST"];
NSData *jsonData = data;
[rq setHTTPBody:jsonData];
[rq setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[rq setValue:[NSString stringWithFormat:#"%ld", (long)[jsonData length]] forHTTPHeaderField:#"Content-Length"];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
//Want to change to a synchronous request
[NSURLConnection sendAsynchronousRequest:rq queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *rsp, NSData *data, NSError *err) {
if (err) {
NSLog(#"Error%#",err);
} else {
NSDictionary *jsonResults = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSNumber *idResponse = jsonResults[#"response"][#"userid"];
if (![idResponse isKindOfClass:[NSNull class]]) {
NSInteger userid = [idResponse integerValue];
}
}
dispatch_async(dispatch_get_main_queue(), ^{
//back in main queue to use results of login.
});
}
}];
}
I think you're out of luck.
Apple has deprecated just about all the methods in NSURLConnection. We're supposed to start using NSURLSession instead, and that is async only.
The take-away is "if you're using synchronous networking, you're doing it wrong."
I think you should probably bite the bullet and do that refactoring. What I do is to create my own methods that take a completion block, and the completion block in NSURLSession calls my methods completion block (from the main thread to make things simple.)
According to Apple, there is a Sync Request function called
+ sendSynchronousRequest:returningResponse:error:
But also, According to Apple, you should never use it from the main thread of a GUI application(explained in the following link)
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/index.html#//apple_ref/occ/clm/NSURLConnection/sendSynchronousRequest:returningResponse:error:
The Async you posted, did finally get back to the Main Thread when the call is finished using
dispatch_get_main_queue()
I would suggest to do some researches on how iOS manage multi-thread using GCD

How to run a NSURLConnection in background in iOS

I am working with an app which is todo list organizer, where user adds notes. I am using coredata DB to store the notes. As I am providing sync feature, I am parsing JSON data to server, and also getting JSON data from server.
I am using NSURLConnection API and its delegate functions
- (void)pushData
{
loop through the notes array and send notes 1 by one
[[request setValue:#"application/json;charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:jsonData];
m_dataPush = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
[m_dataPush start];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
Process response from server, save to core DB
and again pushData if any modified and again process the response
}
I call this API, on appEnterBackground and appBecomeActive, because, I want the data to updated on multiple devices.
The problems, which I am facing is that
1) When the notes are more, app is getting stuck, when we exit and open the app and start adding notes.
2) I tried using GCD, but then my NSURLConnection doesnot send me any response
Regards
Ranjit
Ranjit: Based on your comments in the different responses, I suspect you are sending the 1st request from the main thread. When you receive the 1st response, you process it in the background, and then send the 2nd request also from the background. The subsequent requests should be sent from the main thread
[self performSelectorOnMainThread:#selector(myMethodToOpenConnection:)
withObject:myObject
waitUntilDone:NO];
otherwise the thread exits before the delegate is called
You can use NSOperation Queue with NSURLConnection like this
//allocate a new operation queue
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//Loads the data for a URL request and executes a handler block on an
//operation queue when the request completes or fails.
[NSURLConnection
sendAsynchronousRequest:urlRequest
queue:queue
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *error) {
if ([data length] >0 && error == nil){
//process the JSON response
//use the main queue so that we can interact with the screen
NSString *myData = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
NSLog(#"JSON data = %#", myData);
NSDictionary *myDict = [myData JSONValue];
}
}];
it will do all the processing in the background.
NSURLConnection provides a convenience method called sendAsynchronousRequest: completionHandler: that does the GCD work for you. You can tell it to run the completion handler on the main thread.
Using it, your code would get simpler as follows:
// place a declaration in your .h to make it public
- (void)pushDataWithCompletion:(void (^)(BOOL, NSError*))completion;
- (void)pushDataWithCompletion:(void (^)(BOOL, NSError*))completion
{
// setup your connection request...
[[request setValue:#"application/json;charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:jsonData];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// whatever you do on the connectionDidFinishLoading
// delegate can be moved here
if (!error) {
// did finish logic here, then tell the caller you are done with success
completion(YES, nil);
} else {
// otherwise, you are done with an error
completion(NO, error);
}
}];
}
Exactly what you pass back in the block depends on what the callers care about. It's common to make some aspect of the data you collected one of the block params.
EDIT - I left out the pointer notation (*) after NSError above.
Also, say you have an array of objects that needs to be processed by the server. This method is good for one call. To handle several, lets give it a parameter. Say that each note is an NSString *;
- (void)pushNote:(NSString *)note withCompletion:(void (^)(BOOL, NSError*))completion {
// Code is the same except it forms the request body using the note parameter.
}
If the real task is to do work for several notes, you need a method that calls this one repeatedly, then tells its caller that its done.
- (void)pushNotes:(NSArray *)notes withCompletion:(void (^)(BOOL, NSError*))completion {
// if there are no more notes, we are done
if (!notes.count) return completion(YES, nil);
NSString *nextNote = notes[0];
NSArray *remainingNotes = [notes subarrayWithRange:NSMakeRange(1, notes.count-1)];
[self pushNote:nextNote withCompletion:^(BOOL success, NSError*error) {
// if success, do the rest, or else stop and tell the caller
if (success) {
[self pushNotes:remainingNotes withCompletion:completion];
} else {
completion(NO, error);
}
}];
}

Download Asynchronously

Im kind of new to Objective C and I wondering if anyone could help me (or point me to a tutorial) to download a .plist file to my iOS app then read it, I need the file to be downloaded Asynchronously so it doesn't pause the app while downloading.
The current code I'm using is:
//UERootArray = [[NSArray alloc] initWithContentsOfURL:[NSURL URLWithString:#"file to url"]];
Ive looked a lot online and cannot find any tutorials, I know this is simple but your help would be much appreciated.
Thanks.
You can use NSURLConnection for achieving this.
or
You can simply use GCD for this, like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UERootArray = [[NSArray alloc] initWithContentsOfURL:[NSURL URLWithString:#"file to url"]];
});
If you wanted to know that your App has finished downloading and after that you wanted to perform some action then in that case, you need to write your own custom delegate which will update when app has finished downloading. But for asynchronous downloading you use GCD as mentioned by Midhun. Refer this How to write Custom Delegate?
Below is the sketch of the implementation using NSURLConnection. Note that completionHandler will be called when your download completes (with either OK or an error), and you can call function that processes Array from there.
Other answers provided here are also valid and it's ultimately your call to figure out which fits your case best.
NSURLRequest* theRequest = [NSURL URLWithString:#"file to url"];
[NSURLConnection sendAsynchronousRequest:theRequest
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse* theResponse, NSData* theData, NSError* theError) {
if (theData) {
NSError* err = nil;
id Array [NSPropertyListSerialization propertyListWithData:theData
options:NSPropertyListImmutable
format:NULL
error:&err];
if ([Array isKindOfClass:[NSArray class]) {
// Do whatever you need with downloaded array
} else {
// Error -- wrong data, check err
}
} else {
// Error while downloading, check theError
}
}];
Try this for Download with completion hander.
NSMutableURLRequest *request=[[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:#"http://"]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *response,NSData *data,NSError *error)
{
//do your stuff when downloading complete..
}

Cancel NSData initWithContentsOfURL in NSOperation

I currently have the following code in an NSOperation that has an observer for keyPath "isCancelled":
downloaded = FALSE;
NSURL *url = [NSURL URLWithString:requestString];
dataXML = [[NSData alloc] initWithContentsOfURL:url];
downloaded = TRUE;
I want to make it so that the observeValueForKeyPath function is able to cancel the dataXML continuing or just completely stop the NSOperation once the NSOperation is sent a cancel message. The NSOperation's cancelling operation cancel only notifies the operation that it should stop, but will not force my operation's code to stop.
You can't cancel it.
If you want to be able to cancel the load mid-way through, use NSURLConnection operating in asynchronous mode. It's a bit more work to set up but you can cancel at any point in the download process.
Alternatively, you could use this handy class I wrote that wraps an async NSURLConnection and its delegate in a single method call ;-)
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[[RequestQueue mainQueue] addRequest:request completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (data && error == nil)
{
//do something with your downloaded data
}
}];
//to cancel the download at any time, just say
[[RequestQueue mainQueue] cancelRequest:request];
Easy!
</shamelessSelfPromotion>
Note that the request above is already asynchronous, and the class already manages queuing of multiple requests, so you don't need to (and shouldn't) wrap it in an NSOperationQueue.

Resources