【问题标题】:Downloading a Large File - iPhone SDK下载大文件 - iPhone SDK
【发布时间】:2011-04-29 12:20:01
【问题描述】:

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

.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

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

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

    [theData release];

}

如果有人可以帮助我,我会很高兴!

谢谢,

凯文

【问题讨论】:

  • 我为此编写了一个库,使用您描述的方法。我把它放在这里希望它对某些人有用,或者激励他们编写自己的解决方案。如果你没问题,当然可以。 github.com/thibaultCha/TCBlobDownload

标签: iphone objective-c download large-files


【解决方案1】:

将内存中的NSData *data 替换为NSOutputStream *stream。在-start 创建流以追加并打开它:

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

当数据进入时,将其写入流:

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

完成后,关闭流:

[stream close];

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

【讨论】:

  • 感谢您的回复,但是否仍然可以获得有关下载的信息?比如获取下载了多少数据?我通常只使用: self.data.length 但由于在这种新方法中 NSMutableData 不存在,我不知道如何实现它。另外(因为我是 Objective-c 的新手),我是否完全摆脱了 .h 文件中的 NSMutableData 以及助手类中它的所有实例?
  • 嘿,我在使用这种下载方法时仍然遇到问题。在调试器中它给了我这个错误:“流错误:错误域=NSPOSIXErrorDomain代码=1”操作无法完成。 Operation not allowed" UserInfo=0x148660 {} " 我不知道为什么会出现这种情况。我的路径设置不正确吗?它应该是目录还是文件?如果您能提供示例代码,那就太好了!
  • 发布你的代码(例如gist.github.com),我可以看看。输出流应该是您具有写入权限的目录中的文件,例如您的应用程序的 Documents 目录。听起来问题在于您试图在系统不允许的地方写入。
  • 哦。我试图在越狱设备上写入以下目录:“/var/mobile/Library/Downloads”该目录与异步下载的代码配合得很好。我不知道为什么它会突然停止工作。还有没有办法查看下载了多少数据?请参考我对此的第一条评论。我需要将此信息用于进度条..
  • 您正在接收和写入字节,因此您可以轻松跟踪已下载的字节数。
【解决方案2】:

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

使用这个功能,对我来说很好用。

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

【讨论】:

  • 这将在完成下载后将所有数据写入流。 OP 的问题是使用所有可用内存进行大量下载,您的回答没有解决这个问题。
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 2011-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-12
    • 2012-04-27
    • 1970-01-01
    • 2012-07-31
    相关资源
    最近更新 更多