2010-10-23 16 views
21

Ich verwende Erica Saduns Methode der asynchronen Downloads (Link hier für die Projektdatei: download), aber ihre Methode funktioniert nicht mit Dateien, die eine große Größe (50 MB oder höher) haben. Wenn ich versuche, eine Datei über 50 MB herunterzuladen, stürzt sie normalerweise aufgrund eines Speicherabsturzes ab. Kann ich diesen Code trotzdem optimieren, damit er auch mit großen Dateien funktioniert? Hier ist der Code, den ich in den Download Klassen haben (die bereits in den Download-Link):Herunterladen einer großen Datei - iPhone SDK

.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 

Und schließlich ist dies, wie ich das schreiben Datei mit den zwei Klassen darüber:

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

    [theData release]; 

} 

Wenn jemand mir helfen könnte, wäre ich so froh!

Danke,

Kevin

+2

Ich schrieb eine Bibliothek dafür mit der Methode, die Sie beschrieben haben. Ich setze es hier in der Hoffnung, dass es für einige Leute nützlich sein wird, oder inspiriere sie, ihre eigene Lösung zu schreiben. Wenn Sie damit natürlich einverstanden sind. https://github.com/thibaultCha/TCBlobDownload – thibaultcha

Antwort

29

Ersetzen Sie die In-Memory-NSData *data mit einem NSOutputStream *stream. In -start den Strom erzeugen, um es zu anhängen und öffnen:

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

Da die Daten in kommt, schreiben Sie es in den 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]); 
} 

Wenn Sie fertig sind, schließen Sie den Strom:

[stream close]; 

Ein besserer Ansatz wäre, den Datenstrom zusätzlich zum Daten-Ivar hinzuzufügen, den Helfer als Delegaten des Datenstroms festzulegen, eingehende Daten im Daten-Ivar zu puffern und dann den Inhalt des Daten-Ivar an den Helfer-wh auszugeben Immer dann, wenn der Stream dem Helfer sein raumverfügbares Ereignis sendet und es aus dem Datenbereich löscht.

+0

Danke für die Antwort, aber ist es immer noch möglich, Informationen über den Download zu erhalten? Zum Beispiel, wie viele Daten heruntergeladen wurden? Ich benutze normalerweise nur: self.data.length, aber da in dieser neuen Methode die NSMutableData nicht da ist, weiß ich nicht, wie ich sie implementieren soll. Auch (da ich zu objective-c irgendwie neu bin), werde ich die NSMutableData vollständig in der .h-Datei und alle Instanzen davon in den Hilfsklassen loswerden? – lab12

+0

Hey, ich habe immer noch Probleme mit dieser Download-Methode. Im Debugger gibt es mir diesen Fehler: "Stream Fehler: Fehler Domain = NSPOSIXErrorDomain Code = 1" Der Vorgang konnte nicht abgeschlossen werden. Operation nicht erlaubt "UserInfo = 0x148660 {} " Ich weiß nicht, warum das angezeigt wird. Habe ich den Pfad falsch eingestellt? Soll es ein Verzeichnis oder eine Datei sein? Es wäre GROSS, wenn Sie einen Beispielcode angeben könnten !! – lab12

+0

Geben Sie Ihren Code ein (z. B. [gist.github.com] (http://gist.github.com/)), und ich kann ihn ansehen. Der Ausgabestream sollte eine Datei in einem Verzeichnis sein, auf das Sie Schreibzugriff haben, z. B. das Dokumentenverzeichnis Ihrer App. Es klingt wie das Problem ist, dass Sie versuchen, irgendwo zu schreiben, dass das System nicht zulassen wird. –

3

Ich habe eine leichte Änderung an dem obigen Code.

Verwenden Sie diese Funktion, es funktioniert gut für mich.

- (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

Dies wird alle Daten in einen Stream schreiben, wenn Das Problem, das OP hatte den gesamten verfügbaren Speicher mit sehr großen Downloads verwendet, ist Ihre Antwort nicht adressiert. –

0

Versuchen Sie . Und:

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]; 
Verwandte Themen