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、と呼ばれるインスタンスメソッドを含めることができます。ここにダウンロードロジックを移動することで、ダウンロードに関連する厄介な詳細の一部から正常に分離できます。ただし、同様に重要なこととして、このメソッドはユーザーインターフェイス自体を更新せず、(以下のポイント#5で示す)によって提供されるブロックを持っているため、このメソッドはUIの考慮事項について心配する必要はありません。とにかく、の実装は次のようになります。downloadInQueuedurationTextcellForRowAtIndexPathdownloadInQueuecompletioncellForRowAtIndexPathdownloadInQueuedownloadInQueue

    //
    //  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. メインのViewControllerで、の古い配列を作成するのではなく、たとえば、と呼ばれるwebUrlsこれらのオブジェクトの新しい配列を作成します。もちろん、これらの各オブジェクトのプロパティを設定します。(繰り返しますが、私はのあいまいな名前に夢中ではありませんが、より具体的な提案をするためにあなたのアプリについて十分に知りません。これを好きなように呼んでください。しかし、以下の私のコードは使用します。)RowDataobjectswebURLRowDataobjectsobjects

  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 に答える