2010-10-23 21 views
21

Używam metody Asynchronicznego pobierania Erica Sadun (link tutaj dla pliku projektu: download), jednak jej metoda nie działa z plikami o dużym rozmiarze (50 MB lub więcej). Jeśli spróbuję pobrać plik o rozmiarze przekraczającym 50 MB, zwykle będzie on ulegał awarii z powodu awarii pamięci. Czy mimo to mogę zmodyfikować ten kod, aby działał również z dużymi plikami? Oto kod mam w klasach DownloadHelper (który jest już w link do pobrania):Pobieranie dużego pliku - 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 

I wreszcie to, jak piszę plik z dwiema klasami powyżej:

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

    [theData release]; 

} 

Jeśli ktoś mógłby mi pomóc, byłbym bardzo zadowolony!

Dzięki,

Kevin

+2

Napisałem do tego bibliotekę, używając opisanej metody. Umieszczam to tutaj mając nadzieję, że przyda się to niektórym ludziom lub zainspiruje ich do napisania własnego rozwiązania. Jeśli oczywiście nie masz nic przeciwko temu. https://github.com/thibaultCha/TCBlobDownload – thibaultcha

Odpowiedz

29

Wymienić w pamięci NSData *data z NSOutputStream *stream. W -start utworzyć strumień do dołączania i otwórz go:

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

Ponieważ dane pochodzą, zapisz go do strumienia:

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

Kiedy skończysz, zamknij strumieniowe:

[stream close]; 

Lepszym rozwiązaniem byłoby dodanie strumienia oprócz danych ivar, ustawienie helpera jako delegata strumienia, buforowanie danych przychodzących w danych ivar, a następnie zrzuty zawartości danych ivar do helpera wh enever strumień wysyła pomocnikowi swoje wydarzenie dostępne w przestrzeni i usuwa je z danych ivar.

+0

Dzięki za odpowiedź, ale czy nadal można uzyskać informacje na temat pobierania? Lubisz uzyskiwać ile danych zostało pobranych? Zwykle używam tylko: self.data.length, ale ponieważ w tej nowej metodzie NSMutableData nie ma, nie wiem jak ją zaimplementować. Również (odkąd jestem trochę nowy do obiektywnego c), czy pozbywam się NSMutableData całkowicie w pliku .h i wszystkich jego instancji w klasach pomocnika? – lab12

+0

Hej, nadal mam problemy z używaniem tej metody pobierania. W debugerze pojawia się ten błąd: "stream error: Error Domain = NSPOSIXErrorDomain Code = 1" Operacja nie mogła zostać zakończona. Operacja niedozwolona "UserInfo = 0x148660 {} " Nie wiem, dlaczego tak się pojawia. Czy ścieżka jest ustawiona nieprawidłowo? Czy ma to być katalog lub plik? Byłoby wspaniale, gdybyś mógł podać przykładowy kod !! – lab12

+0

Wpisz swój kod (np. Na stronie [gist.github.com] (http://gist.github.com/)) i mogę na nie spojrzeć. Strumień wyjściowy powinien znajdować się w pliku w katalogu, do którego masz uprawnienia do zapisu, na przykład w katalogu Dokumenty aplikacji. Wygląda na to, że problem polega na tym, że próbujesz napisać gdzieś, na co system nie pozwala. –

3

Mam niewielką modyfikację powyższego kodu.

Użyj tej funkcji, działa dla mnie dobrze.

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

Spowoduje to zapisanie wszystkich danych do strumienia, gdy pobieranie zostało zakończone. Problemem, z jakim związany był OP, była cała dostępna pamięć z bardzo dużymi pobraniami, twoja odpowiedź nie rozwiązuje tego problemu. –

0

Wypróbuj AFNetworking. I:

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