Search code examples
iphoneobjective-cfiledownloaddisk

How to download files directly to disk on the iPhone os?


I would like to download files directly from an URL to the disk using objective-c on the iPhone os.

Currently I am using NSURLConnection to send a synchronousRequest, writing the returned NSData into a file.

How can I change the download handling (still having the request beeing synchronous, it is already in a background thread) to write the data directly to disk, not using memory variables to store the complete content (only small parts)?

A sample code would be appreciated.

Thank you all in advance for your responses!


Solution

  • You can do this, but it's a bit complicated to set up. Here's how I'd do it:

    warning: the following code was typed in a browser and compiled in my head. Also, there's not a lot of error handling. Caveat Implementor.

    //NSURLConnection+DirectDownload.h
    @interface NSURLConnection (DirectDownload)
    
    + (BOOL) downloadItemAtURL:(NSURL *)url toFile:(NSString *)localPath error:(NSError **)error;
    
    @end
    
    //NSURLConnection+DirectDownload.m
    @implementation NSURLConnection (DirectDownload)
    
    + (BOOL) downloadItemAtURL:(NSURL *)url toFile:(NSString *)localPath error:(NSError **)error {
      NSMutableURLRequest * request = [[NSMutableURLRequest alloc] initWithURL:url];
      //configure the request, or leave it as-is
    
      DirectDownloadDelegate * delegate = [[DirectDownloadDelegate alloc] initWithFilePath:localPath];
      NSURLConnection * connection = [[NSURLConnection alloc] initWithRequest:request delegate:delegate];
      [delegate autorelease];
      [request release];
    
      while ([delegate isDone] == NO) {
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
      }
    
      [connection release];
    
      NSError * downloadError = [delegate error];
      if (downloadError != nil) {
        if (error != nil) { *error = [[downloadError retain] autorelease]; }
        return NO;
      }
    
      return YES;
    }
    
    //DirectDownloadDelegate.h
    @interface DirectDownloadDelegate : NSObject {
      NSError *error;
      NSURLResponse * response;
      BOOL done;
      NSFileHandle * outputHandle;
    }
    @property (readonly, getter=isDone) BOOL done;
    @property (readonly) NSError *error;
    @property (readonly) NSURLResponse * response;
    
    @end
    
    //DirectDownloadDelegate.m
    @implementation DirectDownloadDelegate
    @synthesize error, request, done;
    
    - (id) initWithFilePath:(NSString *)path {
      if (self = [super init]) {
        if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
          [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
        }
        [[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil];
        outputHandle = [[NSFileHandle fileHandleForWritingAtPath:path] retain];
      }
      return self;
    }
    
    - (void) dealloc {
      [error release];
      [response release];
      [outputHandle closeFile];
      [outputHandle release];
      [super dealloc];
    }
    
    - (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)anError {
      error = [anError retain];
      [self connectionDidFinishLoading:connection];
    }
    
    - (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)someData {
      [outputHandle writeData:someData];
    }
    
    - (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)aResponse {
      response = [aResponse retain];
    }
    
    - (void) connectionDidFinishLoading:(NSURLConnection *)connection {
      done = YES;
    }
    

    The basic idea is that you create a standard NSURLConnection, which is normally asynchronous, but just block the thread by spinning the runloop yourself until the connection is done. You also use a custom url connection delegate to just pipe any data the connection receives directly to a file.

    You can now do:

    NSError * downloadError = nil;
    BOOL ok = [NSURLConnection downloadItemAtURL:someURL toFile:someFile error:&downloadError];
    if (!ok) {
      NSLog(@"ack there was an error: %@", error);
    } else {
      NSLog(@"file downloaded to: %@", someFile);
    }