I have 4 elements in my NSMutableArray. I have this neat code for downloading files and displaying the file's data in UITextView for testing purposes. Without the for loop, everything is fine. The code that gives me the problem is in this function:
- (void)complexDownload {
int i;
for (i=0; i < downloadArray.count; i++) {
if (isBusy == NO) {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
downloadURL = [downloadArray objectAtIndex:i];
NSLog(#"URL is %#", downloadURL);
NSLog(#"Downloading object at index %i", i);
NSURL *url = downloadURL;
NSURLRequest *theRequest=[NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSURLConnection *theConnection = [NSURLConnection connectionWithRequest:theRequest delegate:self];
if (theConnection) {
self.downloadData = [NSMutableData data];
isBusy = YES;
NSLog(#"Busy value in download cycle equals %i, downloading", isBusy);
} else {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
NSLog(#"Connection failed");
isBusy = NO;
}
}
}
}
I first thought that the problem might be in the isBusy BOOL, but even without the if condition the app crashes. The compiler gives me no error but this one:
Here's the link for the big screenshot.
The rest of the functions are as follows:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[downloadData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *dataString = [[NSString alloc] initWithData:downloadData encoding:NSASCIIStringEncoding];
self.dataTextView.text = dataString;
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
NSLog(#"Download finished!");
isBusy = NO;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"%#", error);
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
All the NSLogged values are more than fine, the Array has got links and all the links are correct.
My guess is that at some point either downloadArray[i] is corrupt at some point, or it's not an NSUrl. The code crashes in CFURLCopyAbsoluteURL() called from [NSURLRequest requestWithURL...].
You take the async api initWithRequest:delegate: and try to make it synchronous by using isBusy flag. This approach is very wrong to begin with, the NSURLConnection class is smart enough, do don't need to use arbitrary flags if you use it properly.
You should seriously reconsider using NSOperations or GCD. If you're planning to do more complicated connectivity programming maybe you should consider using a third party framework like RestKit.
I think it's a problem with url-object in [NSURLRequest requestWithURL:url
It is not necessary to copy the URL in an extra object. Try this:
NSLog(#"URL is %#", [downloadArray objectAtIndex:i]);
NSLog(#"Downloading object at index %i", i);
NSURL *url = [downloadArray objectAtIndex:i];
(or add self. before downloadURL)
If your downloadArray contains NSStrings:
NSURL *url = [NSURL URLWithString:[downloadArray objectAtIndex:i]];
I would recommend using external framework like [ASIHTTPRequest](http://allseeing-i.com/ASIHTTPRequest/
Get rid of the for loop and the isBusy indicator.
In complexDownload always process the first or the last (what ever is more suitable) object only and then remove it from the array. In connectionDidFinish invoke complexDownload again. Use performSelector for that. The wait time may even be 0.0f. By doing so your downloadArray would act as some sort of queue.
Related
I wonder how I can check if a file exist on a server or not, without downloading the data first.
I have around 30 different objects and some of them is connected to a movie on a server. At the moment I use NSData to control if the the URL exist, and then shows the movie, or if it doesn't and then alerts the user that there is no video for that object. The code I use for the moment:
NSString *fPath = [[NSString alloc] initWithFormat:#"http://www.myserver/%#", [rows idNr]];
NSURL *videoURL = [NSURL URLWithString:fPath];
NSData *videoData = [NSData dataWithContentsOfURL:videoURL];
url = [NSURL URLWithString:fPath];
[fPath release];
if (videoData) {
[self performSelectorOnMainThread:#selector(playVideo:) withObject:url waitUntilDone:NO];
} else {
NSLog(#"videodata false");
errorLabel.hidden = NO;
activityView.hidden = YES;
}
"rows idNr" is the name of the object. This method is doing what I want, but the problem is that with NSData it first "downloading" the file, and when the URL is validated as a file, the movie is loading once again in the movieplayer. This means that it takes twice as long to load the file.
Suggestions?
It took me a while to dig out my answer to one of the previous questions on this topic. Quote:
You can use a NSMutableURLRequest to send a HTTP HEAD request
(there’s a method called setHTTPMethod). You’ll get the same
response headers as with GET, but you won’t have to download the whole
resource body. And if you want to get the data synchronously, use the
sendSynchronousRequest… method of NSURLConnection.
This way you’ll know if the file exists and won’t download it all if it does.
Make an URLConnecion object with desired url request and add NSURLConnectionDelegate into .h file like I want to check "yoururl" is exist or not then you need to do is
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString: #"http://www.google.com"]];
NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
and then you can track http status code in delegate function of NSURLConnectionDelegate
-(void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
int code = [(NSHTTPURLResponse *)response statusCode];
if (code == 404)
{
// website not found
// do your stuff according to your need
}
}
You can also find various status code here.
NSError *err;
if ([videoURL checkResourceIsReachableAndReturnError:&err] == NO)
NSLog(#"wops!");
Here's the code for the accepted answer (for your convenience):
How to make call
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlString]];
[request setHTTPMethod:#"HEAD"];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
You could do this by checking the size of the file via an FTP server, using the SIZE command. If the file size is zero then the file simply do not exist.
Check here on how to do this.
You could of course also do this by using a NSURLRequest with NSURLConnection, checking for the status to be either 200 (success) or 404 (failed). The 404 status doesn't have to be that the file doesn't exist though, it could also be that the file just couldn't be retrieved.
I'm creating an app which downloads a .zip file from S3 server.
All works fine. Now I want to be able to interrupt the current download. If I could save the current size (bytes) of the file, I would be able to send a new request with a range header for the other part of the file.
Problem lies in the fact that I cannot determine the size of the 'already' downloaded content, because I can only see the file in my directory when the download is completed. So if I interrupt, there isn't a partial file saved.
At this time I use the following code for this:
-(void) downloadFile:(NSMutableArray*)paramArray withDict:(NSMutableDictionary*)options
{
NSLog(#"DOWNLOAD THREAD STARTED");
NSString * sourceUrl = [paramArray objectAtIndex:0];
NSString * fileName = [paramArray objectAtIndex:1];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *newFilePath = [documentsDirectory stringByAppendingString:fileName];
NSError *error=[[[NSError alloc]init] autorelease];
NSURLConnection *fileURL = [NSData dataWithContentsOfURL: [NSURL URLWithString:sourceUrl]];
BOOL response = [fileURL writeToFile:newFilePath options:NSDataWritingFileProtectionNone error:&error];
if (response == TRUE)
{
NSLog(#"DOWNLOAD COMPLETED");
[self performSelectorOnMainThread:#selector(downloadComplete:withDict:) withObject:paramArray waitUntilDone:YES];
}
else
{
NSLog(#"Something went wrong while downloading file.");
NSString *callback = [NSString stringWithFormat:#"downloadInterrupted('%#');",fileName];
[webView stringByEvaluatingJavaScriptFromString:callback];
}
[pool drain];
}
AsiHTTP isn't an option because there are issues with the PhoneGap I'm using.
A better idea is to download the file asynchronously. This has several advantages: The most important one is that your user interface stays responsive. The user can go on using your application while it is downloading and waiting for the data. If the data you are downloading is absolutely essential for the application, display some sort of loading indicator.
You can easily start the asynchronous download via
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:sourceUrl]];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
Now, how do I get the downloades data in an NSData object? You implement the following delegate methods for self:
-connection:didReceiveData:
-connection:didFailWithError:
-connectionDidFinishLoading:
The idea is that you are notified whenever some data drops in through your connection or anything important else happens (success or failure for exmple). So you are going to declare a temporary NSMutableData object as an instance variable (say downloadData) and write to it until the download is complete. Do not forget to initialize the empty object and declare a property as well!
-connection:didReceiveData: is called whenever some sort of data (that is, a part of your downloaded file) arrives. So you are going to append it to your temporary object like this:
-(void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.downloadData appendData:data];
}
Once the download has finished (successfully), the next delegate method is called:
-(void) connectionDidFinishLoading:(NSURLConnection *)connection {
//do whatever you need to do with the data in self.downloadData
}
If the downloads fails, -connection:didFailWithError: is called. You can then save the temporary object, get its size and resume the download later. [self.downloadData length]; gets you the size in bytes of the data in your object.
You are going to have to use a lower level api.
time to read up on unix socket programming. http://www.fortunecity.com/skyscraper/arpanet/6/cc.htm would be a good start.
It really won't be too hard. honest.
I recommend you to build a method that save data chunk every 1, 2 MB or maybe less in order to resume properly your download and avoid memory crash.
This because if you get an error in your transfer maybe your file could be result corrupted.
Anyway send a range HTML header is pretty simple
NSFileHandle *fileHandler = [NSFileHandle fileHandleForReadingAtPath:dataPreviouslySavedPath];
[fileHandler seekToEndOfFile];
unsigned long long int range = [fileHandler offsetInFile];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:downloadURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
[request setValue:[NSString stringWithFormat:#"bytes=%lli-", range] forHTTPHeaderField:#"Range"];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
Hope this help you.
i want to download the zip file from web but unable to figure out that how it is possible
i can download image /text/xml file but unable to download a zip file
Can someone guide me how to download zip files from web?
Thanks
If you're using NSURLConnection, it works exactly the same way no matter which type the file has.
Example: (typed off of my head, no guarantee that it works this way and you should obviously implement error checking)
- (void) download
{
self.loadedData = [NSMutableData data]; // make 'loadedData' a property of the class
NSURL *url = [NSURL URLWithString:#"http://..."];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:20.0];
[urlRequest setValue:#"Optional User Agent" forHTTPHeaderField:#"User-Agent"];
// shoot it off
NSURLConnection *mainConnection = [NSURLConnection connectionWithRequest:urlRequest delegate:self];
if (nil == mainConnection) {
NSLog(#"Could not create the NSURLConnection object");
}
}
Then you must handle the incoming data in the delegate methods, e.g. to just save your data:
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[loadedData appendData:data];
}
Take a look at the other delegate methods and implement them, you should deal with authentification challenges and fail responses. You can also for example set:
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
in connection:didReceiveResponse: and set it to NO again in connectionDidFinishLoading:.
Im doing a project where I connect to a webpage using the NSURLConnection to be able to monitor the status codes that are returned (200 OK / 404 ERROR). I would like to send the user to the top url www.domain.com if I recieve 404 as status code and if i recieve as 200 status code I would like to load the page in to a webview. I have seen several implementations of this problem by creating a new request but I feel that it is unnecessary since you already received the html in the first request so i would just like to load that HTML in to the webView.
So i try to use the [webView loadHTMLFromString: baseURL:] but it doesn't always work, I have noticed that when i print the NSString with html in the connectionDidFinnishLoading it sometimes is null and when I monitor these cases by printing the html in didReceiveData a random number of the last packets is NULL (differs between 2-10). It is always the same webpages that doesn't get loaded. If I load them to my webView using [webView loadRequest:myRequest] it always works. My implementation looks like this perhaps someone of you can see what Im doing wrong.
I create my first request with a button click.
-(IBAction)buttonClick:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://www.domain.com/page2/apa.html"];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:url]
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if( theConnection )
{
webData = [[NSMutableData data] retain];
}
else
{
}
}
Then I monitor the response code in the didReceiveResponse method by casting the request to a NSHTTPURLResponse to be able to access the status codes and then setting a Bool depending on the status code.
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSHTTPURLResponse *ne = (NSHTTPURLResponse *)response;
if ([ne statusCode] == 200) {
ok = TRUE;
}
[webData setLength: 0];
}
I then check the bools value in connectionDidFinnishLoading. If I log the html NSString I get the source of the webpage so i know that it isn't an empty string.
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString *html = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:#"http://www.domain.com/"];
if (ok) {
[webView loadHTMLString:html baseURL:url];
ok = FALSE;
}
else {
// Create a new request to www.domain.com
}
}
webData is an instance variable and I load it in didReceiveData like this.
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[webData appendData:data];
}
I am assuming that webData is an instance variable on your class. I think your problem is that you are not setting webData to the data you are getting from the NSURLConnection. Try something like this:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[webData appendData:data];
}
If that doesn't do the trick try adding an NSLog() after the NSString *html = [[NSString alloc]... line, like so:
NSLog(#"HTML: %#", html);
That will tell you whether you are getting HTML or not and whether the problem is with your NSURLConnection code or your UIWebView IBOutlet.
You are mixing two ways of performing connection - synchronous and asynchronous too. If the asynchronous finishes before the synchronous, you reinitialize webData to empty string. So even if synchronous populated webData it would be cleared.
// you create asynchronous connection which starts downloading in background immediately
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
// you create another synchronous connection which will block until the URL is downloaded
[NSURLConnection sendSynchronousRequest:theRequest returningResponse: &responseHeader error: nil];
// assuming your theRequest is valid, theConnection is always non-nil, so this is always TRUE
if (theConnection) {
// you reset content of webData, so if your synchronous connection has downloaded something, you throw it away here
webData = [[NSMutableData data] retain];
} else ...
Try to remove unnecessary/buggy call to sendSynchronousRequest:returningResponse:error: and it might work.
Anyway - where do you populate webData with data ? You did implement connection:didReceiveData: and append what comes into webData, right ? If you didn't - well, that's the problem you are seeing.
I need some help regarding the NSURLConnectionDelegate method.
- (void)startDownload {
NSString *URLString = [NSString stringWithFormat:appRecord.imageURLString];
NSURL *url = [NSURL URLWithString:URLString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
imageConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if(imageConnection) {
activeDownload = [NSMutableData data];
}
}
I am using this method to initiate the NSURLConnection, but the
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
is not calling.. Need Help
Thanks in advance,
Shibin
No single answer but:
1) Put some NSLogs in to display the URL and then validate that it is generated correctly and does return data
2) Check that you have properly declared that you conform to the NSURLConnectionDelegate protocol in the .h
3) Are you threading or messing with the runloops ? " Messages to the delegate will be sent on the thread that calls this method. By default, for the connection to work correctly the calling thread’s run loop must be operating in the default run loop mode."
Sorry but do you do the start in your code ? I don't see it in your extract.
There should be a
[imageConnection start]
somewhere in your code to trigger the start of the connection and get your delegate called asynchronously.