我有一个用于测试AFNetworking
API的应用。它从服务器下载文档并将它们放在应用程序的沙盒中。用户可以开始下载,暂停/恢复下载并取消下载。按预期启动,暂停和恢复所有工作。但取消会做一些我不希望的事情。
该应用
表格中的每个单元格代表一个“下载”,这是我的模型。表视图控制器侦听单元格中的抽头,并将消息发送到begin
/ cancel
/ pause
/ resume
下载。我有一个类DownloadManager
来跟踪我的下载模型对象。我有第三课AFFileDownloadAPIClient
(使用AFHTTPClient
作者推荐的AFNetworking
模式。 DownloadManager
会在AFFileDownloadAPIClient
上调用相应的消息,而NSOperation
会在AFHTTPRequestOperation
上调用相应的方法。
守则
下面的方法创建一个新的"Content-Disposition"
,将这些位流传输到一个文件(这很好),并将其抛入队列,为我启动操作。有几点需要注意:1)我在AFFileDownloadAPIClient
标题中添加了一些元数据,如内容长度和生成的文件名,因为下载开始时都不知道。请记住这些位正在流式传输给我。 2)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];
}
方法。请记住,暂停和恢复功能似乎工作正常。
progressView
问题
假设我们想要中途取消下载。我会点击“GO”然后等几秒钟。然后点击“X”取消。下面是前/后图像。 (在左边之前,在右边之后)。
点击“X”后,视图会更改为显示原始“GO”按钮,因此您可以再次尝试,在这种情况下,我将其称为就绪状态(或“之前”)。我不明白的是,当我在刚刚取消的同一个下载中第二次点击“GO”时,我的进度指示器会在原来的1.98 MB处停下来....就好像取消了不删除下载的原始字节,记住它们并继续它停止的地方。为什么呢?
问题
我为这篇冗长的帖子道歉,并感谢你阅读这篇文章......
[编辑1]
为了更新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;
}
个对象。我必须确保我的单元格正确创建,对应于正确的下载(在给定的索引处)并反映下载的确切状态。其中一些可能是矫枉过正,但它似乎运作良好。我没有在仪器中测试这个,看看是否/什么时候我泄漏了什么:
{{1}}
答案 0 :(得分:1)
在取消下载方法中从集合中删除时,似乎正在释放outputStream。但是,由于下载被取消,因此不会对Download实例进行状态更改。如果该对象继续设置totalBytes
和percentageDownloaded
值,则进度视图将继续反映部分下载的状态。