В UITableView лучший способ отменить операции GCD для ячеек, которые вышли за пределы экрана?

у меня естьUITableView который загружает изображения из URL в ячейки асинхронно, используя GCD. Проблема в том, что если пользователь пролистывает 150 строк, 150 операций ставятся в очередь и выполняются. Я хочу удалить из очереди / отменить те, которые прошли мимо и исчезли с экрана.

Как мне это сделать?

Мой код на данный момент (довольно стандартный):

<code>- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

    // after getting the cell...

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if (runQ) {
            NSString *galleryTinyImageUrl = [[self.smapi getImageUrls:imageId imageKey:imageKey] objectForKey:@"TinyURL"];
            NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:galleryTinyImageUrl]];
            dispatch_async(dispatch_get_main_queue(), ^{
                if (imageData != nil) {
                    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
                    cell.imageView.image = [UIImage imageWithData:imageData];
                }
            });
        }
    });
</code>

runQ являетсяBOOL я установилNO наviewWillDisappearчто (я думаю) приводит к быстрому очищению очереди, когда этоUITableView выскакивает навигационный контроллер.

Итак, вернемся к моему первоначальному вопросу: как отменить операции извлечения изображения для ячеек, которые вышли за пределы экрана? Благодарю.

Ответы на вопрос(3)

- [UITableViewCell prepareForReuse]

 Steven02 мая 2012 г., 08:21
Что такое призыв отменить GCD? Если бы вы могли предоставить фрагмент кода, я был бы очень признателен.
 02 мая 2012 г., 09:54
ммм, мой плохой, вы не можете отменить выполнение блока, как только он был отправлен в очередь. Вы должны создать класс (например, ImageFetcher), который будет использовать ASYNCHRONOUS соединение, чтобы вы могли отправить сообщение об отмене.
Решение Вопроса

не выполняйте операции очереди во время прокрутки. Вместо этого загружайте изображения только для видимых строк вviewDidLoad и когда пользователь прекращает прокрутку:

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    for (NSIndexPath *indexPath in [self.tableView indexPathsForVisibleRows]) {
        [self loadImageForCellAtPath:indexPath];
    }
}

Если вы все еще хотите иметь возможность отменить загрузку для невидимых ячеек, вы можете использоватьNSBlockOperation вместо GCD:

self.operationQueue = [[[NSOperationQueue alloc] init] autorelease];
[self.operationQueue setMaxConcurrentOperationCount:NSOperationQueueDefaultMaxConcurrentOperationCount];

// ...

-(void)loadImageForCellAtPath:(NSIndexPath *)indexPath {
    __block NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        if (![operation isCancelled]) {
            NSString *galleryTinyImageUrl = [[self.smapi getImageUrls:imageId imageKey:imageKey] objectForKey:@"TinyURL"];
            NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:galleryTinyImageUrl]];
            dispatch_async(dispatch_get_main_queue(), ^{
                if (imageData != nil) {
                    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
                    cell.imageView.image = [UIImage imageWithData:imageData];
                }
            });
        }
    }];

    NSValue *nonRetainedOperation = [NSValue valueWithNonretainedObjectValue:operation];
    [self.operations addObject:nonRetainedOperation forKey:indexPath];
    [self.operationQueue addOperation:operation];
}

Вотoperations являетсяNSMutableDictionary, Когда вы хотите отменить операцию, вы извлекаете ее по ячейкеindexPath, отмените его и удалите из словаря:

NSValue *operationHolder = [self.operations objectForKey:indexPath];
NSOperation *operation = [operationHolder nonretainedObjectValue];
[operation cancel];
 03 мая 2012 г., 07:14
Хорошая точка зрения. Обновил мой ответ.
 Steven02 мая 2012 г., 23:46
Мне нужно вызвать часть отмены (ваш второй фрагмент кода), когда ячейка выходит из представления. Единственный известный мне метод, который вызывается для этого события, - это объект ячейки.prepreForReuse, чья область не имеет доступа к объекту[self.operations] (или егоindexPath). Я прав? Спасибо, это здорово.
 Steven03 мая 2012 г., 06:35
Я попробовал этот подход и заметил, что приложение резко замедляется. При добавлении операции, мы должны добавить что-то кроме mainQueue?
 03 мая 2012 г., 01:36
Как насчет захвата[self.tableView indexPathsForVisibleRows] а затем перебратьself.operations и отменить все операции, которые не находятся в массиве indexPaths? Вы могли бы сделать это вscrollViewDidEndDecelerating, и, возможноviewWillDisappear.

как они были отправлены. Однако вы можете проверить в диспетчерских блоках, чтобы увидеть, нужно ли продолжать код или нет. В -tableView: cellForRowAtIndexPath: вы можете сделать что-то вроде этого

UIImage *image = [imageCache objectForKey:imageName];
if(image) {
    cell.imageView.image = image;
else {
    dispatch_async(globalQueue, ^{
        //get your image here...
        UIImage *image = ...
        dispatch_async(dispatch_get_main_queue(), ^{
            if([cell.indexPath isEqual:indexPath){
                cell.indexPath = nil;
                cell.imageView.image = image;
                [cell setNeedsLayout];
            }
        });
        [imageCache setObject:image forKey:imageName];
    });
}

В основном используйте кэш изображений (NSMutableDictionary) и попытайтесь получить изображение. Если у вас его нет, отправьте блок и получите его. Затем отправьте обратно в основной поток, проверьте путь индекса, затем установите изображение и, наконец, кэшируйте изображение. В этом примере используется настраиваемая ячейка табличного представления, в которой путь индекса хранится вместе с ней в качестве свойства.

Так что это немного дополнительная работа, чтобы сделать это хорошо, но оно того стоит.

 02 мая 2012 г., 19:21
Хотя это не позволяет перезагрузить уже имеющееся изображение, оно не решает исходную проблему @ Steven, заключающуюся в том, чтоcellForRowAtIndexPath вызывается, когда вы прокручиваете мимо ячеек, на которые вы, возможно, не смотрите. Но вы делаете хорошее замечание, что он должен кэшировать изображения. В зависимости от домена, может быть даже лучше записать кеш на диск.
 Steven02 мая 2012 г., 19:35
К вашему сведению, я делаю кеш. Просто пропустил это для ясности. Благодарю.

Ваш ответ на вопрос