我在- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
委托(delegate)调用中有以下代码:
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个单元后,其余的仅在detailTextLabel中迅速显示为0,并且不加载。有任何想法为什么会这样吗?我没有正确进行线程吗?
最佳答案
有两个想法:
NSOperationQueue
将对服务器的并发请求限制为4或5,而不要使用调度队列。 AVPlayerItem
并尝试进行其他并发操作您服务器的请求。您确实应该保存以前下载的结果,以消除重复请求相同数据的需要。 因此,我可能建议以下内容:
viewDidLoad
中,创建我们将用于下载的NSOperationQueue
。还指定您的服务器将允许多少个并发操作:downloadQueue = [[NSOperationQueue alloc] init];
downloadQueue.maxConcurrentOperationCount = 4; // replace this with an appropriate value for your server
webUrls
,它是一个NSURL
对象的数组。在下面的第4点,我们将讨论退出该数组,并创建一个新的行对象数组。但是在我们可以做到这一点之前,我们应该创建这个新的RowData
对象。每个行对象不仅具有
webURL
,而且还具有其他内容,例如durationText
甚至是AVPlayerItem
本身。 (通过保留这些其他对象属性,当单元格滚动回 View 时,我们不需要重新下载数据。)因此,此新类的公共(public)接口(interface)可能类似于://
// 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
着迷。这有点模棱两可。但是我对您的模型数据的性质了解不足,无法建议一个更好的名称。无论您认为合适如何,都可以随时致电此类(class)。 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];
}
}];
}
didEndDisplayingCell
协议(protocol)的UITableViewDelegate
方法。- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
RowData *rowData = self.objects[indexPath.row];
if ([rowData isDownloading])
[rowData cancelDownload];
}
如果支持iOS的早期版本,则必须使用
UIScrollViewDelegate
协议(protocol)方法(例如 scrollViewDidScroll
)来手动确定哪些单元格已滚动出屏幕(例如indexPathsForVisibleRows
中不包括这些单元格),但是想法是相同的。 顺便说一句,在上面的示例
RowData
中,我保存了AVPlayerItem
。仅当以后需要AVPlayerItem
时,才应该这样做。我们已经保存了duration
,它满足了UITableViewCell
所需的所有功能,但是我想您以后可能想对AVPlayerItem
做些事情,所以我也保存了它。但是,如果以后不再需要该AVPlayerItem
,则不要将其保存在RowData
对象中。另外,我不知道它们的大小,但是您可能想编写一个didReceiveMemoryWarning
,它将迭代您的objects
并将每个项目的playerItem
对象设置为nil
。关于iphone - 为什么在iOS上启动了几个线程后,我的线程似乎失败了?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/14422802/