下载大文件 - iPhone SDK

时间:2022-02-09 10:45:03

I am using Erica Sadun's method of Asynchronous Downloads (link here for the project file: download), however her method does not work with files that have a big size (50 mb or above). If I try to download a file above 50 mb, it will usually crash due to a memory crash. Is there anyway I can tweak this code so that it works with large files as well? Here is the code I have in the DownloadHelper Classes (which is already in the download link):

我正在使用Erica Sadun的异步下载方法(此处链接到项目文件:下载),但是她的方法不适用于具有大尺寸(50 mb或更高)的文件。如果我尝试下载超过50 MB的文件,它通常会因内存崩溃而崩溃。无论如何我可以调整这段代码,以便它也适用于大文件?这是我在DownloadHelper类中的代码(已经在下载链接中):

.h

。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

.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

And finally this is how I write the file with the two classes above it:

最后这就是我用它上面的两个类编写文件的方式:

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

    [theData release];

}

If someone could help me out I would be so glad!

如果有人能帮助我,我会很高兴的!

Thanks,

谢谢,

Kevin

凯文

3 个解决方案

#1


30  

Replace the in-memory NSData *data with an NSOutputStream *stream. In -start create the stream to append and open it:

使用NSOutputStream *流替换内存中的NSData *数据。在-start中创建要追加并打开它的流:

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

As data comes in, write it to the 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]);
}

When you're done, close the stream:

完成后,关闭流:

[stream close];

A better approach would be to add the stream in addition to the data ivar, set the helper as the stream's delegate, buffer incoming data in the data ivar, then dump the data ivar's contents to the helper whenever the stream sends the helper its space-available event and clear it out of the data ivar.

更好的方法是在数据ivar之外添加流,将帮助程序设置为流的委托,缓冲数据ivar中的传入数据,然后每当流向辅助程序发送其空间时,将数据ivar的内容转储给帮助程序 - 可用事件并将其从数据ivar中清除。

#2


3  

I have a slight modification to the above code.

我对上面的代码稍作修改。

Use this function, it works fine for me.

使用此功能,它对我来说很好。

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

#3


0  

Try AFNetworking. And:

尝试AFNetworking。和:

NSString *yourFileURL=@"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];

#1


30  

Replace the in-memory NSData *data with an NSOutputStream *stream. In -start create the stream to append and open it:

使用NSOutputStream *流替换内存中的NSData *数据。在-start中创建要追加并打开它的流:

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

As data comes in, write it to the 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]);
}

When you're done, close the stream:

完成后,关闭流:

[stream close];

A better approach would be to add the stream in addition to the data ivar, set the helper as the stream's delegate, buffer incoming data in the data ivar, then dump the data ivar's contents to the helper whenever the stream sends the helper its space-available event and clear it out of the data ivar.

更好的方法是在数据ivar之外添加流,将帮助程序设置为流的委托,缓冲数据ivar中的传入数据,然后每当流向辅助程序发送其空间时,将数据ivar的内容转储给帮助程序 - 可用事件并将其从数据ivar中清除。

#2


3  

I have a slight modification to the above code.

我对上面的代码稍作修改。

Use this function, it works fine for me.

使用此功能,它对我来说很好。

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

#3


0  

Try AFNetworking. And:

尝试AFNetworking。和:

NSString *yourFileURL=@"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];