Вопрос по ios, objective-c, uitableview, iphone, grand-central-dispatch – В UITableView лучший способ отменить операции GCD для ячеек, которые вышли за пределы экрана?

6

у меня есть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   ответа
0

- [UITableViewCell prepareForReuse]

Что такое призыв отменить GCD? Если бы вы могли предоставить фрагмент кода, я был бы очень признателен. Steven
ммм, мой плохой, вы не можете отменить выполнение блока, как только он был отправлен в очередь. Вы должны создать класс (например, ImageFetcher), который будет использовать ASYNCHRONOUS соединение, чтобы вы могли отправить сообщение об отмене.
7

не выполняйте операции очереди во время прокрутки. Вместо этого загружайте изображения только для видимых строк в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];
Хорошая точка зрения. Обновил мой ответ.
Мне нужно вызвать часть отмены (ваш второй фрагмент кода), когда ячейка выходит из представления. Единственный известный мне метод, который вызывается для этого события, - это объект ячейки.prepreForReuse, чья область не имеет доступа к объекту[self.operations] (или егоindexPath). Я прав? Спасибо, это здорово. Steven
Я попробовал этот подход и заметил, что приложение резко замедляется. При добавлении операции, мы должны добавить что-то кроме mainQueue? Steven
Как насчет захвата[self.tableView indexPathsForVisibleRows] а затем перебратьself.operations и отменить все операции, которые не находятся в массиве indexPaths? Вы могли бы сделать это вscrollViewDidEndDecelerating, и, возможноviewWillDisappear.
2

как они были отправлены. Однако вы можете проверить в диспетчерских блоках, чтобы увидеть, нужно ли продолжать код или нет. В -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) и попытайтесь получить изображение. Если у вас его нет, отправьте блок и получите его. Затем отправьте обратно в основной поток, проверьте путь индекса, затем установите изображение и, наконец, кэшируйте изображение. В этом примере используется настраиваемая ячейка табличного представления, в которой путь индекса хранится вместе с ней в качестве свойства.

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

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

Похожие вопросы