我有这个代码- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
代表电话:
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:[webUrls objectAtIndex:indexPath.row]];
CMTime timeduration = playerItem.duration;
float seconds = CMTimeGetSeconds(timeduration);
NSString *duration = [NSString stringWithFormat:@"%f", seconds];
dispatch_async( dispatch_get_main_queue(), ^{
UITableViewCell *updatecell = [tblView cellForRowAtIndexPath:indexPath];
updatecell.detailTextLabel.text = duration;
[updatecell setNeedsLayout];
});
});
每个单元在后台缓慢地加载seconds
进入updatecell.detailTextLabel.text
在细胞上。问题是在我滚动后,加载大约 3 或 4 个单元格后,其余的单元格很快就会在细节文本标签中显示 0 并且不会加载。
有什么想法吗?我的线程处理不正确吗?
有几点想法:
许多服务器对它们从给定客户端接受的并发请求数量施加限制。我建议你使用NSOperationQueue
将向服务器发出的并发请求数量限制为 4 或 5,而不是使用调度队列。
您可能会使问题变得比需要的更糟糕,因为如果您向下滚动表格视图然后向后滚动,当您重新显示前几个单元格时,您将重新下载AVPlayerItem
并尝试向您的服务器发出额外的并发请求。您确实应该保存以前下载的结果,以避免对相同数据进行冗余的重新请求。
在尝试更新 UI 之前,您当前不会检查刚刚下载的单元格是否仍然可见。你真的应该检查一下。
因此,我可能会提出以下建议:
-
在你的视图控制器中viewDidLoad
,创建NSOperationQueue
我们将用于下载。还指定您的服务器允许的并发操作数:
downloadQueue = [[NSOperationQueue alloc] init];
downloadQueue.maxConcurrentOperationCount = 4; // replace this with an appropriate value for your server
-
之前,你有一个数组,webUrls
,这是一个数组NSURL
对象。在下面的第 4 点中,我们将讨论停用该数组,并创建一个新的行对象数组。但在我们做到这一点之前,我们应该创建这个新的RowData
object.
每个行对象不仅有webURL
,还有其他东西,例如durationText
甚至也许AVPlayerItem
本身。 (通过保留这些其他对象属性,当单元格滚动回到视图中时,我们不需要重新下载数据。)因此这个新类的公共接口可能如下所示:
//
// RowData.h
//
#import <Foundation/Foundation.h>
@class AVPlayerItem;
@interface RowData : NSObject
@property (nonatomic, strong) NSURL *webURL;
@property (nonatomic, strong) NSString *durationText;
@property (nonatomic, strong) AVPlayerItem *playerItem;
@property (nonatomic, getter = isDownloaded, readonly) BOOL downloaded;
@property (nonatomic, getter = isDownloading, readonly) BOOL downloading;
- (void)downloadInQueue:(NSOperationQueue *)queue completion:(void (^)(BOOL success))block;
- (void)cancelDownload;
@end
顺便说一句,我并不热衷于班级名称,RowData
。有点太暧昧了。但我对模型数据的性质了解不够,无法提出更好的名称。请随意为该类命名任何您认为合适的名称。
-
你的新RowData
类可以有一个实例方法,称为downloadInQueue
,执行下载,设置durationText
通过将下载逻辑移至此处,我们成功地隔离了cellForRowAtIndexPath
从一些涉及下载的血腥细节。但同样重要的是,这downloadInQueue
方法不会更新用户界面本身,但它有completion
块提供者cellForRowAtIndexPath
(在下面的第 5 点中进行了演示),所以这downloadInQueue
方法不必担心 UI 方面的考虑。无论如何,实施downloadInQueue
可能看起来像:
//
// RowData.m
//
#import "RowData.h"
#import <AVFoundation/AVFoundation.h>
@interface RowData ()
@property (nonatomic, getter = isDownloaded) BOOL downloaded;
@property (nonatomic, getter = isDownloading) BOOL downloading;
@property (nonatomic, weak) NSOperation *operation;
@end
@implementation RowData
- (void)downloadInQueue:(NSOperationQueue *)queue completion:(void (^)(BOOL success))completion
{
if (!self.isDownloading)
{
self.downloading = YES;
NSOperation *currentOperation = [NSBlockOperation blockOperationWithBlock:^{
BOOL success = NO;
self.playerItem = [AVPlayerItem playerItemWithURL:self.webURL];
if (self.playerItem)
{
success = YES;
CMTime timeduration = self.playerItem.duration;
float seconds = CMTimeGetSeconds(timeduration);
self.durationText = [NSString stringWithFormat:@"%f", seconds];
}
self.downloading = NO;
self.downloaded = YES;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
completion(success);
}];
}];
[queue addOperation:currentOperation];
self.operation = currentOperation;
}
}
- (void)cancelDownload
{
if ([self isDownloading] && self.operation)
{
self.downloading = NO;
[self.operation cancel];
}
}
@end
在您的主视图控制器中,而不是创建旧的数组webUrls
,创建一个新数组RowData
称为的对象,例如objects
。设置webURL
每个人的财产RowData
当然是物体。 (再说一次,我对这个含糊不清的名字并不疯狂objects
,但我对您的应用了解不够,无法提出更具体的建议。随便你怎么称呼它。但我下面的代码将使用objects
.)
-
最后,修改你的cellForRowAtIndexPath
使用这个新的RowData
对象,它是downloadInQueue
方法。另外,请注意,completion
块检查以确保单元格仍然可见:
RowData *rowData = self.objects[indexPath.row];
if ([rowData isDownloaded])
{
cell.detailTextLabel.text = rowData.durationText;
}
else
{
cell.detailTextLabel.text = @"..."; // you really should initialize this so we show something during download or remove anything previously there
[rowData downloadInQueue:self.downloadQueue completion:^(BOOL success) {
// note, if you wanted to change your behavior based upon whether the
// download was successful or not, just use the `success` variable
UITableViewCell *updateCell = [tblView cellForRowAtIndexPath:indexPath];
// by the way, make sure the cell is still on screen
if (updateCell)
{
updateCell.detailTextLabel.text = rowData.durationText;
[updateCell setNeedsLayout];
}
}];
}
-
如果使用 iOS 6,如果您想在单元格滚出屏幕时取消挂起的下载,您可以使用didEndDisplayingCell
的方法UITableViewDelegate
协议。
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
RowData *rowData = self.objects[indexPath.row];
if ([rowData isDownloading])
[rowData cancelDownload];
}
如果支持早期版本的 iOS,您必须使用UIScrollViewDelegate
协议方法,例如scrollViewDidScroll http://developer.apple.com/library/ios/documentation/uikit/reference/uiscrollviewdelegate_protocol/Reference/UIScrollViewDelegate.html#//apple_ref/occ/intfm/UIScrollViewDelegate/scrollViewDidScroll%3a,确定哪些单元格已滚出屏幕(例如,不包含在indexPathsForVisibleRows
)手动,但想法是一样的。
顺便说一句,在我的样本中RowData
上面,我正在保存AVPlayerItem
。仅当您需要时才应该这样做AVPlayerItem
之后。我们已经保存了duration
,这实现了我们所需要的一切UITableViewCell
,但我假设您稍后可能想要对AVPlayerItem
,所以我也保存它。但如果你不需要那个AVPlayerItem
稍后,然后不要将其保存在RowData
目的。另外,我不知道它们有多大,但你可能想写一个didReceiveMemoryWarning
这将迭代你的objects
并设置每个项目的playerItem
反对nil
.
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)