2010-10-23 8 views
21

Estoy usando el método de descargas asincrónicas de Erica Sadun (enlace aquí para el archivo de proyecto: download), pero su método no funciona con archivos que tienen un gran tamaño (50 mb o superior). Si intento descargar un archivo de más de 50 mb, generalmente se bloqueará debido a un bloqueo de memoria. ¿De todos modos puedo modificar este código para que también funcione con archivos de gran tamaño? Aquí está el código que tengo en las clases DownloadHelper (que ya está en el enlace de descarga):Descargando un archivo grande - SDK de iPhone

.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 

Y finalmente esta es la forma en la que escribo archivo con las dos clases encima:

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

    [theData release]; 

} 

¡Si alguien pudiera ayudarme estaría muy contento!

Gracias,

Kevin

+2

Escribí una biblioteca para eso, utilizando el método que describió. Lo expongo aquí con la esperanza de que sea útil para algunas personas, o las inspiro a escribir su propia solución. Si estás bien con eso, por supuesto. https://github.com/thibaultCha/TCBlobDownload – thibaultcha

Respuesta

29

Vuelva a colocar la memoria en NSData *data con un NSOutputStream *stream. En -start crear la corriente para anexar y abrirlo:

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

Como dato entra, escribirlo en la corriente:

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]); 
} 

Cuando haya terminado, cerrar la secuencia:

[stream close]; 

Un mejor enfoque sería agregar la secuencia además del ivar de datos, establecer al ayudante como el delegado de la secuencia, guardar los datos entrantes en el ivar de datos, luego volcar los datos del contenido de los datos en el helper wh Una vez que la secuencia envía al asistente su evento disponible en el espacio y lo elimina de los datos ivar.

+0

Gracias por la respuesta, pero ¿es posible obtener información sobre la descarga también? ¿Como obtener la cantidad de información que se ha descargado? Usualmente solo uso: self.data.length pero dado que en este nuevo método el NSMutableData no está allí, no sé cómo implementarlo. Además (dado que soy algo nuevo en el objetivo-c), ¿me deshago de NSMutableData por completo en el archivo .h y todas las instancias de él en las clases de ayuda? – lab12

+0

Hola, sigo teniendo problemas para usar este método de descarga. En el depurador me da este error: "error de transmisión: Dominio de error = NSPOSIXErrorDomain Code = 1" No se pudo completar la operación. Operación no permitida "UserInfo = 0x148660 {} " No sé por qué aparece esto. ¿Tengo la ruta establecida incorrectamente? ¿Se supone que es un directorio o un archivo? ¡Sería EXCELENTE si pudiera proporcionar un código de ejemplo! – lab12

+0

Publique su código (por ejemplo, en [gist.github.com] (http://gist.github.com/)), y puedo verlo. La secuencia de salida debe estar en un archivo en un directorio al que tenga acceso de escritura, como el directorio Documentos de su aplicación. Parece que el problema es que estás tratando de escribir en algún lugar que el sistema no permita. –

3

Tengo una ligera modificación en el código anterior.

Utilice esta función, funciona bien para mí.

- (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]; 
} 
+0

Esto escribirá todos los datos en una secuencia cuando Se terminó de descargar. El problema que tenía el OP era utilizar toda la memoria disponible con descargas muy grandes, su respuesta no soluciona esto. –

0

Probar AFNetworking. Y:

NSString *[email protected]"http://yourFileURL.zip"; 
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:yourFileURL]]; 
AFURLConnectionOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; 

NSString *cacheDir = [NSSearchPathForDirectoriesInDomains 
          (NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]; 
NSString *filePath = [cacheDir stringByAppendingPathComponent: 
         @"youFile.zip"]; 

operation.outputStream = [NSOutputStream outputStreamToFileAtPath:filePath append:NO]; 

[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) { 
    //show here your downloading progress if needed 
}]; 

[operation setCompletionBlock:^{ 
    NSLog(@"File successfully downloaded"); 
}]; 

[operation start]; 
Cuestiones relacionadas