我有一个用于测试AFNetworking
API 的应用程序。它从服务器下载文档并将它们放置在应用程序的沙箱中。用户可以开始下载、暂停/恢复下载和取消下载。按预期启动、暂停和恢复所有工作。然而,取消做了一些我不期望的事情。
应用程序
表中的每个单元格代表一个“下载”,这是我的模型。表格视图控制器监听单元格中的点击并发送消息到begin
///下载。我有一个类来跟踪我的下载模型对象。我有第三堂课(使用作者推荐的模式)。调用相应的消息,然后在 . 上调用相应的方法。cancel
pause
resume
DownloadManager
AFFileDownloadAPIClient
AFHTTPClient
AFNetworking
DownloadManager
AFFileDownloadAPIClient
NSOperation
编码
下面的方法创建一个新的AFHTTPRequestOperation
,将位流式传输到一个文件(这工作正常),并将它扔到一个队列中,为我启动操作。需要注意的几件事:1)我在"Content-Disposition"
标题中放入了一些元数据,例如内容长度和生成的文件名,因为下载开始时都不知道。请记住,这些位正在流式传输给我。2)AFFileDownloadAPIClient
保留一个带有整数索引键的字典,并且AFHTTPRequestOperation
对于每个下载对应于UITableView
. 我发现这有必要稍后将操作检索到pause
,resume
等...
这是在AFFileDownloadAPIClient
:
- (void)downloadFileWithIndex:(int)index fileName:(NSString *)fileName {
// Using standard request operation
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.inputStream = [NSInputStream inputStreamWithURL:request.URL];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:fileInDocumentsPath append:YES];
// BLOCK level variables //
__weak AFHTTPRequestOperation *weakOperation = operation; // For use in download progress BLOCK
__weak NSDate *startTime = [NSDate date];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSHTTPURLResponse *response = (NSHTTPURLResponse*)weakOperation.response;
NSString *contentDisposition = [[response allHeaderFields] objectForKey:@"Content-Disposition"];
NSArray *dispositionMetadata = [contentDisposition componentsSeparatedByString:@";"];
NSString *fileName = @"<?>";
// 3rd item is file name
if (dispositionMetadata != nil && dispositionMetadata.count == 4)
{
fileName = [dispositionMetadata objectAtIndex:2];
}
if ([_downloadFileRequestDelegate respondsToSelector:@selector(downloadFileRequestFinishedWithData:fileName:atIndex:startTime:)])
[_downloadFileRequestDelegate downloadFileRequestFinishedWithData:responseObject fileName:fileName atIndex:index startTime:startTime];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if ([_downloadFileRequestDelegate respondsToSelector:@selector(downloadFileRequestFailedWithError:atIndex:startTime:)])
[_downloadFileRequestDelegate downloadFileRequestFailedWithError:error atIndex:index startTime:startTime];
}
];
// Check "Content-Disposition" for content-length
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
NSHTTPURLResponse *response = (NSHTTPURLResponse*)weakOperation.response;
NSString *contentDisposition = [[response allHeaderFields] objectForKey:@"Content-Disposition"];
NSArray *dispositionMetadata = [contentDisposition componentsSeparatedByString:@";"];
// 4th item is length
if (dispositionMetadata != nil && dispositionMetadata.count == 4)
{
totalBytesExpectedToRead = [[dispositionMetadata objectAtIndex:3] doubleValue];
}
// Notify the delegate of the progress
if ([_requestProgressDelegate respondsToSelector:@selector(requestDidReceiveBytesForIndex:bytes:totalBytes:)])
[_requestProgressDelegate requestDidReceiveBytesForIndex:index bytes:bytesRead totalBytes:totalBytesExpectedToRead];
}];
// Check to see if operation is already in our dictionary
if ([[self.downloadOperations allKeys] containsObject:[NSNumber numberWithInt:index]] == YES)
[self.downloadOperations removeObjectForKey:[NSNumber numberWithInt:index]];
// Add operation to storage dictionary
[self.downloadOperations setObject:operation forKey:[NSNumber numberWithInt:index]];
// Queue up the download operation. No need to start the operation explicitly
[self enqueueHTTPRequestOperation:operation];
}
现在是cancel
, pause
,resume
方法。请记住,暂停和恢复功能似乎工作得很好。
- (void)cancelDownloadForIndex:(int)index {
AFHTTPRequestOperation *operation = [self.downloadOperations objectForKey:[NSNumber numberWithInt:index]];
if (operation != nil) {
[operation cancel];
// Remove object from dictionary
[self.downloadOperations removeObjectForKey:[NSNumber numberWithInt:index]];
}
}
- (void)pauseDownloadForIndex:(int)index {
AFHTTPRequestOperation *operation = [self.downloadOperations objectForKey:[NSNumber numberWithInt:index]];
if (operation != nil)
[operation pause];
}
- (void)resumeDownloadForIndex:(int)index {
AFHTTPRequestOperation *operation = [self.downloadOperations objectForKey:[NSNumber numberWithInt:index]];
if (operation != nil)
[operation resume];
}
问题
假设我们要在中途取消下载。我会点击“开始”然后等待几秒钟。然后点击“X”取消。下面是之前/之后的图像。(左边之前,右边之后)。
在您点击“X”后,视图会更改为显示原始的“GO”按钮,以便您可以重试,在这种情况下,我将其称为就绪状态(或“之前”)。我不明白的是,当我在刚刚取消的同一个下载中第二次点击“开始”时,我的进度指示器会在原来的 1.98 MB 停止的地方重新拾取....好像取消没有'不要删除下载的原始字节,记住它们并从中断的地方继续。为什么?
问题
- 为什么取消后的下载从中断的地方继续?
- 这种行为是预期的还是意外的?
对于这篇冗长的帖子,我深表歉意,并感谢您阅读本文。
[编辑 1]
为了更新progressView
,UITableViewCell
我至少要做这两件事。
- 使用我称之为的数据模型类
Download
。 - 使用管理我的模型
Download
对象及其状态的数据模型管理器类。
在表格视图控制器中,我在给定索引处侦听给定下载接收到的字节:
- (void)downloadDidReceiveBytesForIndex:(int)downloadIndex bytes:(long long)bytes totalBytes:(double)totalBytes {
NSIndexPath *path = [NSIndexPath indexPathForRow:downloadIndex inSection:0];
DownloadTableViewCell *cell = (DownloadTableViewCell*)[self.tableView cellForRowAtIndexPath:path];
Download *download = [_downloadManager.downloads objectAtIndex:path.row];
download.bytesDownloaded += bytes;
download.percentageDownloaded = download.bytesDownloaded / totalBytes;
// as a factor of 0.0 to 1.0 not 100.
cell.downloadProgressView.progress = download.percentageDownloaded;
float MB_CONVERSION_FACTOR = 0.000000953674;
NSString *bytesText = [NSString stringWithFormat:@"Downloading %.2f of %.2f MB", roundf((download.bytesDownloaded * MB_CONVERSION_FACTOR)*100)/100.0, roundf((totalBytes * MB_CONVERSION_FACTOR)*100)/100.0];
cell.downloadProgressLabel.text = bytesText;
}
最后,为了处理滚动表和UITableViewCell
对象的重用。我必须确保正确创建我的单元格,对应于正确的下载(在给定索引处)并反映下载的确切状态。其中一些可能是矫枉过正,但它似乎运作良好。不过,我还没有在仪器中对此进行测试,以查看是否/何时泄漏任何东西:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
DownloadTableViewCell *cell = (DownloadTableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
UIViewController *temporaryController = [[UIViewController alloc] initWithNibName:@"DownloadTableViewCell" bundle:nil];
// Grab a pointer to the custom cell.
cell = (DownloadTableViewCell *)temporaryController.view;
[cell initState];
// Listens for method calls on cell
cell.delegate = self;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
// Set index for this cell (it could be wrong if cell is re-used)
cell.downloadIndex = indexPath.row;
Download *download = [_downloadManager.downloads objectAtIndex:indexPath.row];
cell.downloading = download.downloading;
cell.nameLabel.text = download.name;
cell.descriptionLabel.text = download.description;
cell.downloadProgressView.progress = download.percentageDownloaded;
// Check for completed status
cell.completed = download.completed;
cell.completedFileNameLabel.text = download.fileName;
return cell;
}