0

我有一个用于测试AFNetworkingAPI 的应用程序。它从服务器下载文档并将它们放置在应用程序的沙箱中。用户可以开始下载、暂停/恢复下载和取消下载。按预期启动、暂停和恢复所有工作。然而,取消做了一些我不期望的事情。

应用程序

表中的每个单元格代表一个“下载”,这是我的模型。表格视图控制器监听单元格中的点击并发送消息到begin///下载。我有一个类来跟踪我的下载模型对象。我有第三堂课(使用作者推荐的模式)。调用相应的消息,然后在 . 上调用相应的方法。cancelpauseresumeDownloadManagerAFFileDownloadAPIClientAFHTTPClientAFNetworkingDownloadManagerAFFileDownloadAPIClientNSOperation

一个简单的 AFNetworking 下载跟踪应用

编码

下面的方法创建一个新的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. 为什么取消后的下载从中断的地方继续?
  2. 这种行为是预期的还是意外的?

对于这篇冗长的帖子,我深表歉意,并感谢您阅读本文。

[编辑 1]

为了更新progressViewUITableViewCell我至少要做这两件事。

  1. 使用我称之为的数据模型类Download
  2. 使用管理我的模型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;
}
4

1 回答 1

1

在取消下载方法中从集合中删除时,似乎正在释放 outputStream。但是,由于下载被取消,下载实例上的状态不会发生变化。如果该对象继续具有totalBytespercentageDownloaded设置值,则进度视图将继续反映部分下载状态。

于 2013-04-19T14:56:00.163 回答