Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Downloading a Large File - iPhone SDK

Tags:

I am using Erica Sadun's method of Asynchronous Downloads (link here for the project file: download), however her method does not work with files that have a big size (50 mb or above). If I try to download a file above 50 mb, it will usually crash due to a memory crash. Is there anyway I can tweak this code so that it works with large files as well? Here is the code I have in the DownloadHelper Classes (which is already in the download link):

.h

@protocol DownloadHelperDelegate <NSObject> @optional - (void) didReceiveData: (NSData *) theData; - (void) didReceiveFilename: (NSString *) aName; - (void) dataDownloadFailed: (NSString *) reason; - (void) dataDownloadAtPercent: (NSNumber *) aPercent; @end  @interface DownloadHelper : NSObject  {     NSURLResponse *response;     NSMutableData *data;     NSString *urlString;     NSURLConnection *urlconnection;     id <DownloadHelperDelegate> delegate;     BOOL isDownloading; } @property (retain) NSURLResponse *response; @property (retain) NSURLConnection *urlconnection; @property (retain) NSMutableData *data; @property (retain) NSString *urlString; @property (retain) id delegate; @property (assign) BOOL isDownloading;  + (DownloadHelper *) sharedInstance; + (void) download:(NSString *) aURLString; + (void) cancel; @end 

.m

#define DELEGATE_CALLBACK(X, Y) if (sharedInstance.delegate && [sharedInstance.delegate respondsToSelector:@selector(X)]) [sharedInstance.delegate performSelector:@selector(X) withObject:Y]; #define NUMBER(X) [NSNumber numberWithFloat:X]  static DownloadHelper *sharedInstance = nil;  @implementation DownloadHelper @synthesize response; @synthesize data; @synthesize delegate; @synthesize urlString; @synthesize urlconnection; @synthesize isDownloading;  - (void) start {     self.isDownloading = NO;      NSURL *url = [NSURL URLWithString:self.urlString];     if (!url)     {         NSString *reason = [NSString stringWithFormat:@"Could not create URL from string %@", self.urlString];         DELEGATE_CALLBACK(dataDownloadFailed:, reason);         return;     }      NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url];     if (!theRequest)     {         NSString *reason = [NSString stringWithFormat:@"Could not create URL request from string %@", self.urlString];         DELEGATE_CALLBACK(dataDownloadFailed:, reason);         return;     }      self.urlconnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];     if (!self.urlconnection)     {         NSString *reason = [NSString stringWithFormat:@"URL connection failed for string %@", self.urlString];         DELEGATE_CALLBACK(dataDownloadFailed:, reason);         return;     }      self.isDownloading = YES;      // Create the new data object     self.data = [NSMutableData data];     self.response = nil;      [self.urlconnection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; }  - (void) cleanup {     self.data = nil;     self.response = nil;     self.urlconnection = nil;     self.urlString = nil;     self.isDownloading = NO; }  - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)aResponse {     // store the response information     self.response = aResponse;      // Check for bad connection     if ([aResponse expectedContentLength] < 0)     {         NSString *reason = [NSString stringWithFormat:@"Invalid URL [%@]", self.urlString];         DELEGATE_CALLBACK(dataDownloadFailed:, reason);         [connection cancel];         [self cleanup];         return;     }      if ([aResponse suggestedFilename])         DELEGATE_CALLBACK(didReceiveFilename:, [aResponse suggestedFilename]); }  - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData {     // append the new data and update the delegate     [self.data appendData:theData];     if (self.response)     {         float expectedLength = [self.response expectedContentLength];         float currentLength = self.data.length;         float percent = currentLength / expectedLength;         DELEGATE_CALLBACK(dataDownloadAtPercent:, NUMBER(percent));     } }  - (void)connectionDidFinishLoading:(NSURLConnection *)connection {     // finished downloading the data, cleaning up     self.response = nil;      // Delegate is responsible for releasing data     if (self.delegate)     {         NSData *theData = [self.data retain];         DELEGATE_CALLBACK(didReceiveData:, theData);     }     [self.urlconnection unscheduleFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];     [self cleanup]; }  - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {     self.isDownloading = NO;     NSLog(@"Error: Failed connection, %@", [error localizedDescription]);     DELEGATE_CALLBACK(dataDownloadFailed:, @"Failed Connection");     [self cleanup]; }  + (DownloadHelper *) sharedInstance {     if(!sharedInstance) sharedInstance = [[self alloc] init];     return sharedInstance; }  + (void) download:(NSString *) aURLString {     if (sharedInstance.isDownloading)     {         NSLog(@"Error: Cannot start new download until current download finishes");         DELEGATE_CALLBACK(dataDownloadFailed:, @"");         return;     }      sharedInstance.urlString = aURLString;     [sharedInstance start]; }  + (void) cancel {     if (sharedInstance.isDownloading) [sharedInstance.urlconnection cancel]; } @end 

And finally this is how I write the file with the two classes above it:

- (void) didReceiveData: (NSData *) theData {     if (![theData writeToFile:self.savePath atomically:YES])         [self doLog:@"Error writing data to file"];      [theData release];  } 

If someone could help me out I would be so glad!

Thanks,

Kevin

like image 591
lab12 Avatar asked Oct 23 '10 07:10

lab12


People also ask

How do I download large files from AWS?

Streaming to a file The most straightforward method to download larger files with the AWS SDK for iOS is to use the outputStream property of the S3GetObjectRequest . Setting this property to an already opened stream will prevent the SDK from caching the entire file in memory.


2 Answers

Replace the in-memory NSData *data with an NSOutputStream *stream. In -start create the stream to append and open it:

stream = [[NSOutputStream alloc] initToFileAtPath:path append:YES]; [stream open]; 

As data comes in, write it to the stream:

NSUInteger left = [theData length]; NSUInteger nwr = 0; do {     nwr = [stream write:[theData bytes] maxLength:left];     if (-1 == nwr) break;     left -= nwr; } while (left > 0); if (left) {     NSLog(@"stream error: %@", [stream streamError]); } 

When you're done, close the stream:

[stream close]; 

A better approach would be to add the stream in addition to the data ivar, set the helper as the stream's delegate, buffer incoming data in the data ivar, then dump the data ivar's contents to the helper whenever the stream sends the helper its space-available event and clear it out of the data ivar.

like image 106
Jeremy W. Sherman Avatar answered Sep 25 '22 18:09

Jeremy W. Sherman


I have a slight modification to the above code.

Use this function, it works fine for me.

- (void) didReceiveData: (NSData*) theData {        NSOutputStream *stream=[[NSOutputStream alloc] initToFileAtPath:self.savePath append:YES];     [stream open];     percentage.hidden=YES;     NSString *str=(NSString *)theData;     NSUInteger left = [str length];     NSUInteger nwr = 0;     do {         nwr = [stream write:[theData bytes] maxLength:left];         if (-1 == nwr) break;         left -= nwr;     } while (left > 0);     if (left) {         NSLog(@"stream error: %@", [stream streamError]);     }     [stream close]; } 
like image 21
Rahul Juyal Avatar answered Sep 24 '22 18:09

Rahul Juyal