3

我在- (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];
    });
});

每个单元格在后台缓慢地加载secondsupdatecell.detailTextLabel.text单元格上。问题是在我滚动后,加载了大约 3 或 4 个单元格后,其余的只是在 detailTextLabel 中快速显示 0 并且不加载。

任何想法为什么会这样?我没有正确地穿线吗?

4

1 回答 1

8

几个想法:

  1. 许多服务器对它们从给定客户端接受的并发请求数量施加了限制。我建议您使用NSOperationQueue将您对服务器发出的并发请求数限制为 4 或 5,而不是使用调度队列。

  2. 您可能会使问题变得比需要的更糟,因为如果您向下滚动表格视图然后备份,当您重新显示前几个单元格时,您正在重新下载AVPlayerItem并尝试发出额外的并发请求你的服务器。您确实应该保存以前下载的结果,以消除重复请求相同数据的需要。

  3. 在尝试更新 UI 之前,您当前没有检查刚刚下载的单元格是否仍然可见。你真的应该检查一下。

所以,我可能会提出以下建议:

  1. 在您的视图控制器中viewDidLoad,创建NSOperationQueue我们将用于下载的。还要指定您的服务器将允许多少并发操作:

    downloadQueue = [[NSOperationQueue alloc] init];
    downloadQueue.maxConcurrentOperationCount = 4; // replace this with an appropriate value for your server
    
  2. 之前,您有一个数组 ,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
    

    顺便说一句,我对类名并不疯狂,RowData. 这有点太模棱两可了。但我对您的模型数据的性质知之甚少,无法建议一个更好的名称。随意调用这个类你认为是合适的。

  3. 您的新RowData类可以有一个名为 的实例方法,downloadInQueue它执行下载、设置durationText适当的等。通过将下载逻辑移到此处,我们成功地隔离cellForRowAtIndexPath了与下载有关的一些血腥细节。不过同样重要的是,此downloadInQueue方法不会更新用户界面本身,而是它具有completioncellForRowAtIndexPath(在下面的第 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
    
  4. 在您的主视图控制器中,与其创建旧数组,不如webUrls创建一个由这些RowData对象组成的新数组,例如,objects. webURL当然,为每个RowData对象设置属性。(再一次,我对 的模棱两可的名称并不感到疯狂objects,但我对您的应用程序的了解不够多,无法提出更具体的建议。您可以随意调用它。但我下面的代码将使用objects。)

  5. 最后,修改您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];
            }
        }];
    }
    
  6. 如果使用 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,手动确定哪些单元格已滚动出屏幕(例如未包含在 中indexPathsForVisibleRows),但想法是相同的。

顺便说一句,在我上面的示例中RowData,我正在保存AVPlayerItem. 仅当您需要AVPlayerItem后者时才应该这样做。我们已经保存了duration,它实现了我们对 的所有需要UITableViewCell​​,但我假设您以后可能想要对 做一些事情AVPlayerItem,所以我也保存了它。但是,如果您AVPlayerItem以后不需要它,请不要将其保存在RowData对象中。另外,我不知道它们有多大,但是您可能想编写一个didReceiveMemoryWarning将遍历您objects并将每个项目的playerItem对象设置为nil.

于 2013-01-20T14:42:25.217 回答