17

I need to download large file (i.e. > 40MB) to my application from server, this file will be ZIP or PDF. I achieved it using NSURLConnection, that works well if the file is smaller other wise it download a part of the fill and the execution has stopped. for example I tried to download 36MB PDF file, but but 16MB only downloaded. pls help me to know the reason? how to fix it?

FYI: Implementation File

#import "ZipHandlerV1ViewController.h"
@implementation ZipHandlerV1ViewController
- (void)dealloc
{
    [super dealloc];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}
 - (void)viewDidLoad
 {
     UIView *mainView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 400, 400)];
     [mainView setBackgroundColor:[UIColor darkGrayColor]];

     UIButton *downloadButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
     [downloadButton setFrame:CGRectMake(50, 50, 150, 50)];
     [downloadButton setTitle:@"Download File" forState:UIControlStateNormal];
     [downloadButton addTarget:self action:@selector(downloadFileFromURL:) forControlEvents:UIControlEventTouchUpInside];
     [mainView addSubview:downloadButton];

     [self setRequestURL:@"http://www.mobileveda.com/r_d/mcps/optimized_av_allpdf.pdf"];
     [self.view addSubview:mainView];

     [super viewDidLoad];
 }

- (void)viewDidUnload
{
    [super viewDidUnload];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}


-(void) setRequestURL:(NSString*) requestURL {
    url = requestURL;
}

-(void) downloadFileFromURL:(id) sender {
    NSURL *reqURL =  [NSURL URLWithString:url];

    NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:reqURL];
    NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
    if (theConnection) {
       webData = [[NSMutableData data] retain];
    } else {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error !" message:@"Error has occured, please verify internet connection"  delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil];

        [alert show];
        [alert release];
    }
}

-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    [webData setLength:0]; 
}

-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [webData appendData:data];
}

-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
   NSString *fileName = [[[NSURL URLWithString:url] path] lastPathComponent];
    NSArray *pathArr = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *folder = [pathArr objectAtIndex:0];

    NSString *filePath = [folder stringByAppendingPathComponent:fileName];
    NSURL *fileURL = [NSURL fileURLWithPath:filePath];
    NSError *writeError = nil;

    [webData writeToURL: fileURL options:0 error:&writeError];
    if( writeError) {
        NSLog(@" Error in writing file %@' : \n %@ ", filePath , writeError );
        return;
    }
    NSLog(@"%@",fileURL);
}

-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error !" message:@"Error has occured, please verify internet connection.."  delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil];

    [alert show];
    [alert release];

}

@end

Header File:

#import <UIKit/UIKit.h>
@interface ZipHandlerV1ViewController : UIViewController {
    NSMutableData *webData;
    NSString *url;
}
-(void) setRequestURL:(NSString*) requestURL;
@end

Thank you,

Updated: This happens because of my downloadable file was in shared hosting which having the download limitations. After I moved that file to dedicated server that working fine. and also I tried to download some other files like videos from some other sites, that also working fine. So, if you having problem like partial download, don't only stick with the code, check the server too

Jayabal
  • 3,619
  • 3
  • 24
  • 32

4 Answers4

23

You are currently keeping all downloaded data in a NSMutableData object which is kept within the RAM of your device. That will, depending on the device and the available memory, at some point trigger a memory warning or even a crash.

To get such large downloads to work, you will have to write all downloaded data directly to the filesystem.

-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 
{
   //try to access that local file for writing to it...
   NSFileHandle *hFile = [NSFileHandle fileHandleForWritingAtPath:self.localPath];
   //did we succeed in opening the existing file?
   if (!hFile) 
   {   //nope->create that file!
       [[NSFileManager defaultManager] createFileAtPath:self.localPath contents:nil attributes:nil];
       //try to open it again...
       hFile = [NSFileHandle fileHandleForWritingAtPath:self.localPath];
   }
   //did we finally get an accessable file?
   if (!hFile)
   {   //nope->bomb out!
       NSLog("could not write to file %@", self.localPath); 
       return;
   }
   //we never know - hence we better catch possible exceptions!
   @try 
   {
       //seek to the end of the file
       [hFile seekToEndOfFile];
       //finally write our data to it
       [hFile writeData:data];
   }
   @catch (NSException * e) 
   {
       NSLog("exception when writing to file %@", self.localPath); 
       result = NO;
   }
   [hFile closeFile];
}
Till
  • 27,559
  • 13
  • 88
  • 122
  • even after doing the changes what you have mentioned, It downloads only 6.4MB of 39MB file, Why its happens so? is there any memory limitation?.., I'm tried it in both the simulator and the iPad device. – Jayabal Jun 09 '11 at 05:42
  • This should overwrite the file each time didReceiveData is invoked. – Vladimir Obrizan Apr 17 '13 at 14:26
  • 1
    @Vova you are correct, my example was really just a hint and not fully covering the needs. I changed that and now it takes extra care for all possible situations. – Till Apr 17 '13 at 19:31
  • @Till how can I obtain the self.localPath? – Balz Dec 20 '13 at 17:53
7

I had the same problem, and seems that I discovered some solution.

In your header file declare:

NSMutableData *webData;
NSFileHandle *handleFile;

In your .m file on downloadFileFromURL when you get the connection initiate the NSFileHandle:

if (theConnection) {

        webData = [[NSMutableData data] retain];

        if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
            [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
        }

        handleFile = [[NSFileHandle fileHandleForWritingAtPath:filePath] retain];
    }

then in didReceiveData instead of appending data to memory write it to disk, like this:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {

    [webData appendData:data];

    if( webData.length > SOME_FILE_SIZE_IN_BYTES && handleFile!=nil) {          
        [handleFile writeData:recievedData];
        [webData release];
        webData =[[NSMutableData alloc] initWithLength:0];
    }
}

when the download finish on connectionDidFinishLoading add this lines to write the file and to release connection:

[handleFile writeData:webData];
[webData release];
[theConnection release];

I'm trying it right now, hope it works..

Frade
  • 2,938
  • 4
  • 28
  • 39
  • I'm not sure why you are using the NSMutableData instead of just using the NSFileHandle, but I was able to take this example and download a 700MB file to my iPad without ever storing any of the data in memory. Thanks! – Joseph DeCarlo Dec 01 '12 at 22:04
  • @JosephDeCarlo, one good reason to have NSMutableData is to reduce the number of I/O operations. – Vladimir Obrizan Apr 18 '13 at 16:36
  • I guess it depends on what size file you are trying to download and what your resource constraints are. – Joseph DeCarlo Apr 20 '13 at 21:49
  • 1
    [handleFile writeData:recievedData]; -> [handleFile writeData:webData]; ? – ThinkBonobo Apr 22 '14 at 15:55
2

This happens because of my downloadable file was in shared hosting which having the download limitations. After I moved that file to dedicated server that working fine. and also I tried to download some other files like videos from some other sites, that also working fine.

So, if you having problem like partial download, don't only stick with the code, check the server too.

Jayabal
  • 3,619
  • 3
  • 24
  • 32
1

If you're willing to use asi-http-request, it's much, much easier.

Check out https://github.com/steipete/PSPDFKit-Demo for a working example with asi.

It's about as easy as this:

    // create request
    ASIHTTPRequest *pdfRequest = [ASIHTTPRequest requestWithURL:self.url];
    [pdfRequest setAllowResumeForFileDownloads:YES];
    [pdfRequest setNumberOfTimesToRetryOnTimeout:0];
    [pdfRequest setTimeOutSeconds:20.0];
    [pdfRequest setShouldContinueWhenAppEntersBackground:YES];
    [pdfRequest setShowAccurateProgress:YES];
    [pdfRequest setDownloadDestinationPath:destPath];

    [pdfRequest setCompletionBlock:^(void) {
        PSELog(@"Download finished: %@", self.url);

        // cruel way to update
        [XAppDelegate updateFolders];
    }];

    [pdfRequest setFailedBlock:^(void) {
        PSELog(@"Download failed: %@. reason:%@", self.url, [pdfRequest.error localizedDescription]);
    }];
steipete
  • 7,581
  • 5
  • 47
  • 81
  • Thanks steipete, ya now I'm using ASIHttpRequest for past one week, and I'm new to iPhone dev. I've an another doubt,that how to pause & resume download and how can we find out that the entire file has downloaded successfully or not? is there possible in ASIHttpRequest?, Thanks again.. – Jayabal Aug 02 '11 at 04:52
  • I'm not sure this is built in - you may have to override ASIHttpRequest for doing so. – steipete Aug 02 '11 at 06:03
  • True that. I'm currently writing on an alternative with AFNetworking. Hope they'll integrate it with 1.0. – steipete Feb 07 '12 at 23:34