Implement Asynchronous Download (NSURLConnection) on separate NSThread? - iphone

This may sounds weird but please bear with me. I have 6-7 API calls which make request to a server one by one. I want to implement these calls in a separate thread. But when I do this, none of my delegate methods (of NSURLConnection) gets called even after managing a separate NSRunloop
([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];)
Can anyone suggests me alternative approach for the same or any correction in existing functionality??

Use ASIHTTPRequest instead. It's much easier to use than NSURLConnection.

a quick google threw up this: http://blog.emmerinc.be/index.php/2009/03/15/multiple-async-nsurlconnections-example/ he is using a dictionary to manage multiple requests

Using a separate thread for each NSURLConnection, which is already multithreaded is a bad idea. It's just pointlessly using system resources and defeating NSURLConnections attempts to manage connections optimally. However, it does work so if you are not receiving delegate messages you are doing something wrong. Rather than find an alternative way todo it you should try to get to the bottom of your problem with the runloop.

I'm using ASIHTTPRequest to do the similar operation. Go through the following code change the downloadAllIcons method to suit your requirement,
[NSThread detachNewThreadSelector:#selector(downloadAllIcons:) toTarget:self withObject:xmlData];
-(void) downloadAllIcons:(NSData *)_xmlData
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *IconList= PerformXMLXPathQuery(_xmlData,#"//icon");
[[NSUserDefaults standardUserDefaults] setObject:IconList forKey:#"IconList"];
for (int i=0; i<[IconList count]; i++) {
if ([[NSUserDefaults standardUserDefaults] objectForKey:[[IconList objectAtIndex:i] objectForKey:#"nodeContent"]]==nil) {
NSData * responseData=[self downloadProccessedImage:[[IconList objectAtIndex:i] objectForKey:#"nodeContent"]];
if(responseData)
[[NSUserDefaults standardUserDefaults] setObject:responseData forKey:[[IconList objectAtIndex:i] objectForKey:#"nodeContent"]];
//NSLog(#"%#",[[IconList objectAtIndex:i] objectForKey:#"nodeContent"]);
}
}
[pool release];
}
-(id) downloadProccessedImage:(NSString *)_URL
{
NSData *response=nil;
NSURL *url = [NSURL URLWithString:_URL];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setTimeOutSeconds:60];
[ASIHTTPRequest setShouldThrottleBandwidthForWWAN:YES];
[request startSynchronous];
NSError *error = [request error];
if (!error)
{
response = [request responseData];
}
return response;
}

Related

Implementation of Async Request causing many leaks

I've inherited a project that uses of ASIHttpRequest for all network communication. I am unclear as to which specific version we're using. All I can tell is that, from the .h files, the oldest creation date on a particular file is 17/08/10 (ASIDataDecompressor).
We're using completion and failure blocks. For some reason, the failure block is often triggered, which should only really happen if the server fails to respond. Our logs look sane, and we haven't received any notifications (Airbrake) that there were server problems around the time the errors occur, so for now I'm moving forward with the assumption that our server is fine and it's the app that is the culprit.
I decided to run the app through Instruments (Leaks) and was astonished to see that when I force a request to fail, ~27 leaks are created immediately. I'm don't know how to get around Instruments all that well, so I'm not really sure what to do with the information now that I have it.
I figured I'd post my code to see if there's anything glaring.
In viewDidLoad, this code is executed
[[MyAPI sharedAPI] getAllHighlights:pageNumber:perPage onSuccess:^(NSString *receivedString,NSString *responseCode) {
[self getResults:receivedString];
if(![responseCode isEqualToString:#"Success"]) {
[self hideProgressView];
appDelegate.isDiscover_RefreshTime=YES;
[[MyAPI sharedAPI] showAlert:responseCode];
} else {
NSString *strLogEvent=#"Discover_Highlights_Loaded Page_";
strLogEvent=[strLogEvent stringByAppendingFormat:#"%i",intPageNumber];
[FlurryAnalytics logEvent:strLogEvent timed:YES];
}
} onFail:^(ASIFormDataRequest *request) {
NSDictionary *parameters = [[MyAPI sharedAPI] prepareFailedRequestData:request file:#"Discover" method:_cmd];
[FlurryAnalytics logEvent:#"Unable_to_Connect_to_Server" withParameters:parameters timed:true];
[self hideProgressView];
appDelegate.isDiscover_RefreshTime=YES;
[[AfarAPI sharedAPI] showAlert:#"Unable to Connect to Server."];
[tblHighlightsGrid reloadData];
[tblListHighlights reloadData];
}];
These typedefs have been defined at the top of API Singleton:
typedef void (^ASIBasicBlockWrapper)(NSString *responseString,NSString *responseCode);
typedef void (^ASIBasicBlockWrapperFail)(ASIFormDataRequest *request);
MyAPISingleton#getAllHighlights...
- (void)getAllHighlights:(NSString *)pageNumber:(NSString *)perPage onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2{
NSString *access_token= [[NSUserDefaults standardUserDefaults] objectForKey:#"access_token"];
NSString *url = [baseURL stringByAppendingFormat:AFAR_GET_ALL_HIGHLIGHTS_ENDPOINT, pageNumber,perPage];
if (access_token) { url = [url stringByAppendingFormat:ACCESS_TOKEN, access_token]; }
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:url]];
[request setRequestMethod:#"GET"];
[request setDelegate:self];
[self executeAsynchronousRequest:request onSuccess:cb1 onFail:cb2];
}
And finally, MyAPI#executeAsynchronousRequest:
- (void) executeAsynchronousRequest:(ASIFormDataRequest *)request onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2
{
[request setCompletionBlock:^{
int statusCode = [request responseStatusCode];
NSString *statusMessage = [self statusErrorMessage:statusCode];
cb1([request responseString],statusMessage);
}];
[request setFailedBlock:^{
cb2(request);
}];
[request startAsynchronous];
}
Does anything stand out as to why 27 leaks are created?
I figured this out.
The ASIHttpRequest Documentation is very clear about the fact that you need to designate your request object with the __block storage mechanism:
Note the use of the __block qualifier when we declare the request, this is important! It tells the block not to retain the request, which is important in preventing a retain-cycle, since the request will always retain the block.
In getAllHighlights(), I'm doing that, but then I'm sending my request object as an argument to another method (executeAsyncRequest). The __block storage type can only be declared on local variables, so in the method signature, request is just typed to a normal ASIFormDataRequest, and so it seems as though it loses its __block status.
The trick is to cast (I'm not sure if that's technically accurate) the argument before using it in a block.
Here's my leak free implementation of executeAsyncRequest:
- (void) executeAsyncRequest:(ASIFormDataRequest *)request onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2
{
// this is the important part. now we just need to make sure
// to use blockSafeRequest _inside_ our blocks
__block ASIFormDataRequest *blockSafeRequest = request;
[request setCompletionBlock: ^{
int statusCode = [blockSafeRequest responseStatusCode];
NSString *statusMessage = [self statusErrorMessage:statusCode];
cb1([blockSafeRequest responseString],statusMessage);
}];
[request setFailedBlock: ^{
cb2(blockSafeRequest);
}];
[request startAsynchronous];
}

How to return data gotten from a web service in objective- c (iPhone)?

This might be a dumb question. Sorry if it is.
But Im working on a project that consumes web services. I can connect to the web service and get the data I need fine.
I would like to have a method that returns this data obtained from the web service to the caller. The only problem is that the data is only obtained inside the ConnectionDidFinishLoading method, and I can't access this data from my method.
here is my code, that works fine:
- (NSData *) dataForMethod:(NSString *)webMethod withPostString:(NSString *)postString
{
NSURL *url = [NSURL URLWithString:[SigameWebServiceAddress stringByAppendingFormat:#"%#%#", #"/", webMethod]];
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
NSString *msgLength = [NSString stringWithFormat:#"%d", [postString length]];
[req addValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[req addValue:msgLength forHTTPHeaderField:#"Content-Length"];
[req setHTTPMethod:#"POST"];
[req setHTTPBody: [postString dataUsingEncoding:NSUTF8StringEncoding]];
conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
if (conn)
{
webData = [NSMutableData data];
}
// I WOULD LIKE TO RETURN WEBDATA TO THE CALLER HERE, BUT WEBDATA IS EMPTY NOW, THE
//connectionDidFinishLoading ONLY GETS CALLED WITH THE DATA I WANT AFTER THE COMPILER
//IS DONE EXECUTING MY METHOD.
}
-(void) connection:(NSURLConnection *) connection didReceiveResponse:(NSURLResponse *) response
{
[webData setLength: 0];
}
-(void) connection:(NSURLConnection *) connection didReceiveData:(NSData *) data
{
[webData appendData:data];
}
-(void) connection:(NSURLConnection *) connection didFailWithError:(NSError *) error
{
NSLog(#"FATAL ERROR");
}
-(void) connectionDidFinishLoading:(NSURLConnection *) connection
{
NSLog(#"DONE. Received Bytes: %d", [webData length]);
NSString *theXML = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
//---shows the XML---
NSLog(#"%#", theXML); //NOW, THIS IS THE DATA I WANT. BUT HOW CAN I RETURN THIS TO
//THE CALLER. I MEAN, THE CALLER THAT CALLED MY METHOD
//+ (NSData *) dataForMethod: withPostString:
}
Any help here is appreciated!
Thanks
There are really two ways to go about this.
Create a delegate interface
Use Blocks
I would strongly advise against using the synchronous methods - unless you are/have created your own asynchronous framework around them (i.e. you are manually starting another thread and executing your synchronous request on that thread). In the long run you will realize you need the requests to be async, and you'll have to re-work everything such that they are.
To give a quick overview of the two options I gave:
1. Create a delegate interface
The idea here is to create a class which performs the request, and create a protocol the caller must implement. When the request is complete, you will invoke a specified method on the delegate with the data:
The protocol might look something like this:
#protocol RequestClassDelegate <NSObject>
- (void)requestCompleted:(ResponseClass *)data;
- (void)requestError:(NSError *)error;
#end
The class which makes the request might look something like this:
#interface RequestClass : NSObject
- (void)makeRequest:(id<RequestClassDelegate>)delegate;
#end
And the request class implementation might contain some of the following, in addition to your connection logic:
#implementation RequestClass
{
__weak id<RequestClassDelegate> _delegate;
}
// Connection Logic, etc.
- (void)makeRequest:(id<RequestClassDelegate>)delegate
{
_delegate = delegate;
// Initiate the request...
}
-(void) connectionDidFinishLoading:(NSURLConnection *) connection
{
NSString *theXML = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
// Processing, etc.
// Here we'll call the delegate with the result:
[_delegate requestCompleted:theResult];
}
#end
2. Use Blocks
This solution is much the same as the first solution - but, a bit more elegant in my opinion. Here, we'll change the RequestClass to use blocks instead of a delegate:
typedef void (^requestCompletedBlock)(id);
typedef void (^requestErrorBlock)(NSError *);
#interface RequestClass : NSObject
#property (nonatomic, copy) requestCompletedBlock completed;
#property (nonatomic, copy) requestErrorBlock errored;
- (void)makeRequest:(requestCompletedBlock)completed error:(requestErrorBlock)error;
#end
And the implementation of that might look something like this:
#implementation RequestClass
#synthesize completed = _completed;
#synthesize errored = _errored;
// Connection Logic, etc.
- (void)makeRequest:(requestCompletedBlock)completed error:(requestErrorBlock)error
{
self.completed = completed;
self.errored = error;
// Initiate the request...
}
-(void) connectionDidFinishLoading:(NSURLConnection *) connection
{
NSString *theXML = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
// Processing, etc.
// Here we'll call the delegate with the result:
self.completed(theResult);
}
#end
It sounds like you are trying to use return the data synchronously from your method, but you are using an asynchronous method (using an NSURLConnection and presumably calling its start method) to begin retrieving data. If you really want your method to return its result synchronously, read on. As #Steve says in another answer, however, you may also reconsider your interface design and instead implement it using an asynchronous approach and use his recommendations for either a delegate or block-based interface.
If you want to return the data synchronously from your method, use a synchronous request. So change this part of your code:
conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
[conn start]; // I presume you have this somewhere
if (conn)
{
webData = [NSMutableData data];
}
with something more like this:
NSURLResponse *response = nil;
NSError *error = nil;
webdata = [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error];
if (webdata) {
return webdata;
}
else {
// Handle error by looking at response and/or error values
return nil;
}
You will no longer need any of your delegate code if you use this approach. You will be limited in some ways though. For example, if your web service requires authentication via something other than URL parameters you can't use this approach.
Steve's answer is great and I can only suggest the way using blocks. Actually, as I am new into Objective-C I implemented the approach steve outlined. It works perfectly.
The Post for more details and my own point of view you can find here:
http://kerkermeister.net/how-to-build-an-cocos2d-ios-app-communicating-with-a-restful-api-the-sequence/
The Post contains all the tiny steps you need to follow to get Steve's solution approach with blocks working. That includes:
- an updateable view that will render information as soon as retrieved from Web API asynchronously
- a controller invoking the HTTP request to the Web API
- the actual HttpRequest class that uses iOS standard NSURLConnections
- a model class that uses blocks as callbacks to update its data
Your going to have to either implement a separate method in which you use the data once the data has been returned by the connectionDidFinishLoading method or make the request synchronously. The reason I believe the above does not work is because the request is happening on a separate thread, so the main thread continues, but does not actually have the data.
This is a good way to do that if synchronous is what you want:
Does all NSURLConnections connect asynchronously? iOs
In order to download data from webserivce - use NSURLSession -
A URL session task that returns downloaded data directly to the app in memory.
// 1. create NSURL link to your webservice
NSString *dataUrl = #"DATA_LINK_TO_WEBSERVICE";
NSURL *url = [NSURL URLWithString:dataUrl];
// 2. create a NSURLSessionDataTask
NSURLSessionDataTask *downloadTask = [[NSURLSession sharedSession]
dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//Handle response here
}];
// 3.resume the task
[downloadTask resume];
Refernces:
apple documentation refrence:
https://developer.apple.com/documentation/foundation/nsurlsessiondatatask?language=objc
Raywanderlich great cookbook:
https://www.raywenderlich.com/2392-cookbook-using-nsurlsession
Your going to need to parse the XML that comes back. There are some good Objective C XML parsers out there. One in particular is made for ease of use....
http://nfarina.com/post/2843708636/a-lightweight-xml-parser-for-ios
It's a very light weight parser for extracting the values you want from XML. I've used many times with great success and little hassle. Here is how I query a web address and turn it into data.
NSString *query = [NSString stringWithFormat:#"http://WEB_ADDRESS_FOR_XML];
NSURL *URL = [NSURL URLWithString:query];
NSData *data = [NSData dataWithContentsOfURL:URL];
Or with NSURLConnection, in the did receive data:
-(void) connection:(NSURLConnection *) connection didReceiveData:(NSData *) data
{
//USE THE DATA RETURNED HERE....
}
Then use the Parser from my link to get the contents:
SMXMLDocument *document = [SMXMLDocument documentWithData:data error:NULL];
NSLog("\nXML Returned:%#",document);

Converting from ASIHTTPRequest to AFNetworking

I am converting my app routines from ASIHTTP to AFNetworking due to the unfortunate discontinuation of work on that project ... and what I found out later to be the much better and smaller codebase of AFNetworking.
I am finding several issues. My code for ASIHTTPRequest is built as a method. This method takes a few parameters and posts the parameters to a url ... returning the resulting data. This data is always text, but in the interests of making a generic method, may sometimes be json, sometimes XML or sometimes HTML. Thus I built this method as a standalone generic URL downloader.
My issue is that when the routine is called I have to wait for a response. I know all the "synchronous is bad" arguments out there...and I don't do it a lot... but for some methods I want synchronous.
So, here is my question. My simplified ASIHTTP code is below, followed by the only way i could think of coding this in AFNetworking. The issue I have is that the AFNetworking sometimes does not for the response before returning from the method. The hint that #mattt gave of [operation waitUntilFinished] totally fails to hold the thread until the completion block is called... and my other method of [queue waitUntilAllOperationsAreFinished] does not necessarily always work either (and does NOT result in triggering the error portion of the [operation hasAcceptableStatusCode] clause). So, if anyone can help, WITHOUT The ever-present 'design it asynchronously', please do.
ASIHTTP version:
- (NSString *) queryChatSystem:(NSMutableDictionary *) theDict
{
NSString *response = [NSString stringWithString:#""];
NSString *theUrlString = [NSString stringWithFormat:#"%#%#",kDataDomain,kPathToChatScript];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:theUrlString]];
for (id key in theDict)
{
[request setPostValue:[theDict objectForKey:key] forKey:key];
}
[request setNumberOfTimesToRetryOnTimeout:3];
[request setAllowCompressedResponse:YES];
[request startSynchronous];
NSError *error = [request error];
if (! error)
{
response = [request responseString];
}
return response;
}
AFNetworking version
- (NSString *) af_queryChatSystem:(NSMutableDictionary *) theDict
{
NSMutableDictionary *theParams = [NSMutableDictionary dictionaryWithCapacity:1];
for (id key in theDict)
{
[theParams setObject:[theDict objectForKey:key] forKey:key];
}
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:kDataDomain]];
NSMutableURLRequest *theRequest = [httpClient requestWithMethod:#"POST" path:[NSString stringWithFormat:#"/%#",kPathToChatScript] parameters:theParams];
__block NSString *responseString = [NSString stringWithString:#""];
AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:theRequest] autorelease];
operation.completionBlock = ^ {
if ([operation hasAcceptableStatusCode]) {
responseString = [operation responseString];
NSLog(#"hasAcceptableStatusCode: %#",responseString);
}
else
{
NSLog(#"[Error]: (%# %#) %#", [operation.request HTTPMethod], [[operation.request URL] relativePath], operation.error);
}
};
NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
[queue addOperation:operation];
[queue waitUntilAllOperationsAreFinished];
[httpClient release];
return responseString;
}
Thanks very much for any ideas.
- (void)af_queryChatSystem:(NSMutableDictionary *) theDict block:(void (^)(NSString *string))block {
...
}
Now within the completionBlock do:
block(operation.responseString);
block will act as the delegate for the operation. remove
-waitUntilAllOperationsAreFinished
and
return responseString
You call this like:
[YourInstance af_queryChatSystem:Dict block:^(NSString *string) {
// use string here
}];
Hope it helps. You can refer to the iOS example AFNetworking has
I strongly recommend to use this opportunity to convert to Apple's own NSURLConnection, rather than adopt yet another third party API. In this way you can be sure it won't be discontinued. I have found that the additional work required to get it to work is minimal - but it turns out to be much more robust and less error prone.
My solution is manually to run the current thread runloop until the callback have been processed.
Here is my code.
- (void)testRequest
{
MyHTTPClient* api = [MyHTTPClient sharedInstance]; // subclass of AFHTTPClient
NSDictionary* parameters = [NSDictionary dictionary]; // add query parameters to this dict.
__block int status = 0;
AFJSONRequestOperation* request = [api getPath:#"path/to/test"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject) {
// success code
status = 1;
NSLog(#"succeeded");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// failure
status = 2;
NSLog(#"failed");
}];
[api enqueueHTTPRequestOperation:request];
[api.operationQueue waitUntilAllOperationsAreFinished];
while (status == 0)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate date]];
}
STAssertEquals(status, 1, #"success block was executed");
}

UIProgressView, Theading, performSelectorOnMainThread while using ASIHTTPRequest

Kinda stuck on this problem and I'm not sure, where I've gone wrong. Heres what I'm doing:
Class calls:
- (void)updateApplicationDataInBackground {
updateView = [[UpdatingView alloc] init];
[self.view addSubview:updateView.view];
DataSynchronizer *dataSynchronizer = [[DataSynchronizer alloc] init];
[NSThread detachNewThreadSelector:#selector(initWithDataRequest:) toTarget:dataSynchronizer withObject:self];
[dataSynchronizer release];
This creates a thread to retrieve data from the server and parse it. In DataSynchronizer this is the method being called:
- (void)initWithDataRequest:(id)parent {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
controller = parent;
NSLog(#"DataSynchronizer initWithDataRequest called");
NSURL *url = [NSURL URLWithString: ApiUrl];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:ApiKey forKey:#"key"];
[request setPostValue:ApiPass forKey:#"password"];
[request setPostValue:#"somevalue" forKey:#"framework"];
[request setPostValue:#"somevalue" forKey:#"method"];
[request setDidFinishSelector:#selector(parseResult:)];
[request setDidFailSelector:#selector(requestError:)];
[request setTimeOutSeconds:60];
[request setDelegate:self];
[request startAsynchronous];
[pool release];
After my data is received I parse the contents and do my data synch. This is all working as expected. I've decided to throw in a UIProgressView so the user can see what is going on with this request, this progress view lives in updateView which is created in the updateApplicationDataInBackground.
I'm not trying to show progress for the web service call but simply when milestones are reached in the data processing. In the DidFinishSelector its calling parseResult
There are five method its calls with the response data:
[self parseData:[data objectForKey:#"types"] forObject:[Types class] andParent:nil];
[controller performSelectorOnMainThread:#selector(updateProgress:) withObject:[NSNumber numberWithFloat:.4] waitUntilDone:YES];
After each process I'm trying to update the UIProgressView, it will never update. Now if I simply call performSelectorOnMainThread from outside the ASIHTTPRequest it works as expected, but not within the DidFinishSelector. I've tried many variations on this where it calls a local method which updates the mainThread, where I simply use performSelector. Nothing works, how do I update the the UIProgessView?
Is the problem a thread spawning a thread?
Thanks
EDIT:
Looks like the DidFinishSelector is being called on the main thread already. I've updated my code to simply call:
[controller updateProgress:[NSNumber numberWithFloat:.8]]
Still no luck....
Realized it might be helpful to see the UIProgessView update method.
- (void)updateProgress:(NSNumber *)progress {
float newProgess = [progress floatValue];
[updateView.myProgress setProgress: newProgess];
Ok so it looks like I found my own answer after changing somethings around. Because ASIHttpRequest performs SetDidFinish selector on the main thread my calls performSelectorOnMainThread weren't doing anything. I changed my initial call for the DataSynchronizer to the main thread and added changed the DidFinish method to:
- (void)parseDataInBackground:(ASIHTTPRequest *)request {
[NSThread detachNewThreadSelector:#selector(parseResult:) toTarget:self withObject:request];
Which then makes the parse method run on separate thread (since its the bulk of the processing and now performOnMainThread works without issue.

iOS Application Background Downloading

Hey! I need to know how I can have my iOS Application start a download in the background of the application (like, have the download run in the AppDelegate file) so changing ViewControllers will not interrupt or cancel the download. I also need to be able to get the progress of the download (0.00000 - 1.00000), to set a UIProgressView object to, which also means I need a - (void)progressDidChangeTo:(int)progress function.
Just use ASIHTTPRequest it is way easier than NSURLRequest and does exactly what you need.
It examples that shows how to download in background and how to report progress.
I wouldn't download anything in the AppDelegate directly. Instead I would create a separated class just for that purpose. Let's call it MyService I would then initialize that class in my app delegate.
The class can work as a singleton or can be passed to each view controller that requires it.
In MyService class I would add the ASINetworkQueue and few methods to handle the requests when they are ready. Here is the code from ASI examples that you can use:
- (IBAction)startBackgroundDownloading:(id)sender
{
if (!self.queue) {
self.queue = [[[ASINetworkQueue alloc] init] autorelease];
}
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setDidFinishSelector:#selector(requestDone:)];
[request setDidFailSelector:#selector(requestWentWrong:)];
[self.queue addOperation:request]; //queue is an NSOperationQueue
[self.queue go];
}
- (void)requestDone:(ASIHTTPRequest *)request
{
NSString *response = [request responseString];
//Do something useful with the content of that request.
}
- (void)requestWentWrong:(ASIHTTPRequest *)request
{
NSError *error = [request error];
}
If you need to set the progress bar. I would just expose the setDownloadProgressDelegate of ASINetworkQueue in my MyService class and set it in my ViewControllers like that:
[[MyService service] setDownloadProgressDelegate: self.myUIProgressView];
BTW. If you need to continue downloading even when your app exits you can set ShouldContinueWhenAppEntersBackground property of your request to YES.
you can use NSURLConnection to start an asynchronous request that won't cause your UI to be frozen. You can do it by doing something like:
NSURLRequest *urlRequest = [[NSURLRequest alloc] initWithURL:url];
connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
[urlRequest release];
in order to have your progress you can use the:
connection:didReceiveResponse:(NSURLResponse *)response;
delegate call to inspect the response.expectedContentLength and then use the
connection:didReceiveData:(NSData *)data
to track the amount of data that was downloaded and calculate a percentage.
Hope this helps,
Moszi