2010-10-23 38 views
21

Tôi đang sử dụng phương pháp tải xuống không đồng bộ của Erica Sadun (liên kết ở đây cho tệp dự án: download), tuy nhiên phương pháp của cô không hoạt động với các tệp có kích thước lớn (50 mb trở lên). Nếu tôi cố tải xuống tệp có dung lượng trên 50 mb, tệp sẽ thường bị lỗi do sự cố bộ nhớ. Có anyway tôi có thể tinh chỉnh mã này để nó hoạt động với các tập tin lớn không? Đây là mã tôi có trong lớp DownloadHelper (đó là đã có trong liên kết tải xuống):Tải xuống một tệp lớn - 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 

Và cuối cùng đây là cách tôi viết tệp có hai lớp ở trên nó:

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

    [theData release]; 

} 

Nếu ai đó có thể giúp tôi, tôi sẽ rất vui!

Cảm ơn,

Kevin

+2

Tôi đã viết thư viện cho điều đó, sử dụng phương pháp bạn mô tả. Tôi đặt nó ở đây hy vọng nó sẽ hữu ích cho một số người, hoặc truyền cảm hứng cho họ viết giải pháp của riêng họ. Nếu bạn là ok với nó tất nhiên. https://github.com/thibaultCha/TCBlobTải xuống – thibaultcha

Trả lời

29

Thay thế trong bộ nhớ NSData *data với một NSOutputStream *stream. Trong -start tạo dòng để thêm và mở nó:

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

Theo dữ liệu do thỏa thuận, hãy viết nó vào dòng:

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

Khi bạn đã hoàn tất, đóng dòng:

[stream close]; 

Một cách tiếp cận tốt hơn là thêm luồng ngoài ivar dữ liệu, đặt trình trợ giúp làm đại biểu của luồng, dữ liệu đệm vào dữ liệu ivar, sau đó đổ nội dung của ivar dữ liệu vào trình trợ giúp enever dòng sẽ gửi helper sự kiện có sẵn không gian của nó và xóa nó ra khỏi ivar dữ liệu.

+0

Cảm ơn bạn đã phản hồi, nhưng vẫn có thể nhận thông tin về tải xuống không? Giống như nhận được bao nhiêu dữ liệu đã được tải xuống? Tôi thường chỉ sử dụng: self.data.length nhưng vì trong phương thức mới này, NSMutableData không có ở đó, tôi không biết cách thực hiện nó. Ngoài ra (vì tôi là loại mới để khách quan-c), tôi có được thoát khỏi NSMutableData hoàn toàn trong tập tin .h và tất cả các trường hợp của nó trong các lớp học trợ giúp? – lab12

+0

Tôi vẫn gặp sự cố khi sử dụng phương thức tải xuống này. Trong trình gỡ rối, nó cung cấp cho tôi lỗi này: "lỗi luồng: Miền lỗi = NSPOSIXErrorDomain Code = 1" Không thể hoàn tất thao tác. Thao tác không được phép "UserInfo = 0x148660 {} " Tôi không biết tại sao điều này xuất hiện. Tôi có đặt đường dẫn không chính xác không? Có phải đó là thư mục hoặc tệp không? Nó sẽ là tuyệt vời nếu bạn có thể cung cấp một mã ví dụ !! – lab12

+0

Đăng mã của bạn (ví dụ: tại [gist.github.com] (http://gist.github.com/)) và tôi có thể xem mã đó. Luồng đầu ra phải là một tệp trong thư mục mà bạn có quyền truy cập ghi, chẳng hạn như thư mục Tài liệu của ứng dụng. Có vẻ như vấn đề là bạn đang cố gắng viết một nơi nào đó mà hệ thống sẽ không cho phép. –

3

Tôi có một sửa đổi nhỏ đối với mã trên.

Sử dụng chức năng này, nó hoạt động tốt cho tôi.

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

Điều này sẽ ghi tất cả dữ liệu vào luồng khi Vấn đề OP đã sử dụng tất cả bộ nhớ có sẵn với tải rất lớn, câu trả lời của bạn không giải quyết vấn đề này. –

0

Hãy thử AFNetworking. Và:

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]; 
Các vấn đề liên quan