如何等待下载大文件?

Gre*_*ter 4 objective-c ios ios5

我有一个成功使用同步方法下载文件的应用程序(NSData的initWithContentsOfURL和NSURLConnection的sendSynchronousRequest),但现在我需要支持大文件.这意味着我需要逐位流式传输到磁盘.即使流式传输到磁盘并变为异步也应该是完全正交的概念,Apple的API迫使我去异步才能流式传输.

为了清楚起见,我的任务是允许更大的文件下载,而不是重新构建整个应用程序更加异步友好.我没有资源.但我承认,依赖于重新架构的方法是有效和良好的.

所以,如果我这样做:

NSURLConnection* connection = [ [ NSURLConnection alloc ] initWithRequest: request delegate: self startImmediately: YES ];
Run Code Online (Sandbox Code Playgroud)

..我最终有自己的didReceiveResponse和didReceiveData.优秀.但是,如果我尝试这样做:

NSURLConnection* connection = [ [ NSURLConnection alloc ] initWithRequest: request delegate: self startImmediately: YES ];
while( !self.downloadComplete )
    [ NSThread sleepForTimeInterval: .25 ];
Run Code Online (Sandbox Code Playgroud)

从未调用过didReceiveResponse和didReceiveData.而且我已经弄明白了为什么.奇怪的是,异步下载发生在我正在使用的同一主线程中.因此,当我睡觉主线程时,我也正在睡觉做这项工作的事情.无论如何,我已经尝试了几种不同的方法来实现我想要的东西,包括告诉NSURLConnection使用不同的NSOperationQueue,甚至做dispatch_async来创建连接并手动启动它(我不知道这是怎么回事 - 我一定不能做得对,但似乎没有任何效果.编辑:我做得不对的是了解Run Loops如何工作,以及您需要在辅助线程中手动运行它们.

等待文件下载完成的最佳方法是什么?

编辑3,工作代码:以下代码实际上有效,但如果有更好的方法,请告诉我.

在原始线程中执行的代码,用于设置连接并等待下载完成:

dispatch_queue_t downloadQueue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 );
dispatch_async(downloadQueue, ^{
    self.connection = [ [ NSURLConnection alloc ] initWithRequest: request delegate: self startImmediately: YES ];
    [ [ NSRunLoop currentRunLoop ] run ];
});

while( !self.downloadComplete )
    [ NSThread sleepForTimeInterval: .25 ];
Run Code Online (Sandbox Code Playgroud)

代码在响应连接事件的新线程中执行:

-(void)connection:(NSURLConnection*) connection didReceiveData:(NSData *)data {
    NSUInteger remainingBytes = [ data length ];
    while( remainingBytes > 0 ) {
        NSUInteger bytesWritten = [ self.fileWritingStream write: [ data bytes ] maxLength: remainingBytes ];
        if( bytesWritten == -1 /*error*/ ) {
            self.downloadComplete = YES;
            self.successful = NO;
            NSLog( @"Stream error: %@", self.fileWritingStream.streamError );
            [ connection cancel ];
            return;
        }
        remainingBytes -= bytesWritten;
    }
}

-(void)connection:(NSURLConnection*) connection didFailWithError:(NSError *)error {
    self.downloadComplete = YES;
    [ self.fileWritingStream close ];
    self.successful = NO;
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    self.downloadComplete = YES;
    [ self.fileWritingStream close ];
    self.successful = YES;
}
Run Code Online (Sandbox Code Playgroud)

Cal*_*leb 5

从未调用过didReceiveResponse和didReceiveData.而且我已经弄明白了为什么.奇怪的是,异步下载发生在我正在使用的同一主线程中.它不会创建新线程.因此,当我睡觉主线程时,我也正在睡觉做这项工作的事情.

究竟.连接由运行循环驱动; 如果你睡眠线程,运行循环停止,这会阻止你的连接做它的事情.

所以不要做任何特别的事情.让应用程序坐在那里,运行循环运行.也许在屏幕上放一点微调器以娱乐用户.如果可以的话,开展您的业务.如果可能的话,让用户继续使用该应用程序.连接完成后将调用您的委托方法,然后您可以执行数据所需的操作.

当您将代码移动到后台线程时,您将再次需要一个运行循环来驱动连接.因此,您将开始创建一个运行循环,安排您的连接,然后返回.运行循环将继续运行,并且在连接完成时将再次调用您的委托方法.如果线程完成,则可以停止运行循环并让线程退出.这里的所有都是它的.

示例:让我们用具体的术语来说明这一点.假设您想要一次创建多个连接.将URL粘贴在可变数组中.创建一个名为(例如)的方法startNextConnection,它执行以下操作:

  • 从数组中获取URL(在此过程中将其删除)

  • 创建一个URL请求

  • 启动NSURLConnection

  • 返回

此外,特别是实现必要的NSURLConnectionDelegate方法connectionDidFinishLoading:.请使用该方法执行以下操作:

  • 将数据存储在某处(将其写入文件,将其交给另一个线程进行解析,无论如何)

  • 呼叫 startNextConnection

  • 返回

如果错误从未发生过,那就足以检索列表中所有URL的数据.(当然,你需要startNextConnection足够聪明才能在列表为空时返回.)但是错误确实发生了,所以你必须考虑如何处理它们.如果连接失败,您是否要停止整个过程?如果是这样,只需让你的connection:didFailWithError:方法做一些合适的事情,但不要打电话startNextConnection.如果出现错误,是否要跳到列表中的下一个URL?然后...didFailWithError:打个电话startNextRequest.

替代方案:如果您真的想要保持同步代码的顺序结构,那么您将拥有类似的东西:

[self downloadURLs];
[self waitForDownloadsToFinish];
[self processData];
...
Run Code Online (Sandbox Code Playgroud)

那么你将不得不在另一个线程中进行下载,以便你可以自由地阻止当前线程.如果这是你想要的,那么用运行循环设置下载线程.接下来,使用-initWithRequest:delegate:startImmediately:您正在执行的操作创建连接,但传入NO最后一个参数.使用-scheduleInRunLoop:forMode:添加到下载线程的运行循环的连接,然后开始用的连接-start方法.这使您可以自由地睡眠当前线程.让连接委托的完成例程设置一个标志,self.downloadComplete例如示例中的标志.