Eth*_*len 3 iphone multithreading cocoa-touch grand-central-dispatch ios
我在- (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];
});
});
Run Code Online (Sandbox Code Playgroud)
在背景中,每个细胞缓慢地加载seconds
到updatecell.detailTextLabel.text
细胞上.问题是在我滚动之后,在加载了大约3或4个单元格之后,其余的只是在detailTextLabel中快速显示0并且不加载.
任何想法为什么会这样?我没有正确地进行线程化吗?
几点想法:
许多服务器对从给定客户端接受的并发请求数量进行约束.我建议您使用NSOperationQueue
来限制您对服务器的并发请求数量为4或5,而不是使用调度队列.
您可能会使问题变得更糟,因为如果您向下滚动表格视图然后备份,当您重新显示前几个单元格时,您将重新下载AVPlayerItem
并尝试进行其他并发请求你的服务器.您确实应该保存以前下载的结果,以消除对相同数据的冗余重新请求的需要.
在尝试更新UI之前,您目前尚未检查刚刚下载的单元格是否仍然可见.你真的应该检查一下.
所以,我可能会建议如下:
在视图控制器中viewDidLoad
,创建NSOperationQueue
我们将用于下载的内容.还要指定服务器允许的并发操作数:
downloadQueue = [[NSOperationQueue alloc] init];
downloadQueue.maxConcurrentOperationCount = 4; // replace this with an appropriate value for your server
Run Code Online (Sandbox Code Playgroud)之前,你有一个数组,webUrls
它是一个NSURL
对象数组.在下面的第4点中,我们将讨论退出该数组,并创建一个新的行对象数组.但在我们能够做到这一点之前,我们应该创建这个新RowData
对象.
每个行对象不仅包含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
Run Code Online (Sandbox Code Playgroud)
顺便说一句,我并不是因为班级名字而疯狂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
Run Code Online (Sandbox Code Playgroud)在主视图控制器中,不是创建旧的数组,而是创建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];
}
}];
}
Run Code Online (Sandbox Code Playgroud)如果使用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];
}
Run Code Online (Sandbox Code Playgroud)
如果支持iOS的早期版本,则必须使用UIScrollViewDelegate
协议方法,例如scrollViewDidScroll
,确定哪些单元格已从屏幕滚动(例如,未包含在内indexPathsForVisibleRows
),但想法是相同的.
顺便说一句,在RowData
上面的示例中,我正在保存AVPlayerItem
.你应该只在你需要的时候才这样做AVPlayerItem
.我们已经保存了duration
,它实现了我们所需要的一切UITableViewCell
,但我想你以后可能会想要做一些事情AVPlayerItem
,所以我也保存了.但是如果你AVPlayerItem
以后不再需要它,那就不要把它保存在RowData
对象中.另外,我不知道它们有多大,但你可能想写一个didReceiveMemoryWarning
会遍历你objects
并将每个项目的playerItem
对象设置为的nil
.
归档时间: |
|
查看次数: |
268 次 |
最近记录: |