Асинхронная загрузка изображения UITableViewCell с использованием GCD

В настоящее время я пытаюсь загрузить список UITableView Flickr Photo (cs193p iOS Stanford, назначение 5). Чтобы избежать события блокировки пользовательского интерфейса, я отложил загрузку миниатюр каждой ячейки в другую очередь (но обновите интерфейс обратно в основной очереди). Этот код не асинхронно загружает изображения, хотя добавляет миниатюру, как только я нажимаю строку UITableViewCell. (см. скриншоты ниже). Любая идея, что я делаю неправильно?

PS: Я уже рассмотрел несколько других вопросов, связанных с stackoverflow, и пример Apple LazyTableImages, но я по-прежнему убежден, что это самый чистый способ добиться желаемого результата.

Спасибо!

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Photo List Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    // Configure the cell
    NSDictionary *photo = [self.photoList objectAtIndex:indexPath.row];


    if (photo != nil) {
        if ([[photo objectForKey:@"title"] length] > 0) {
            cell.textLabel.text = [photo objectForKey:@"title"];
        } else if ([[[photo objectForKey:@"description"] objectForKey:@"_content"] length] > 0) {
            cell.textLabel.text = [[photo objectForKey:@"description"] objectForKey:@"_content"];
        } else {
            cell.textLabel.text = @"Unknown";
        }
    }
    cell.imageView.image = [[UIImage alloc] initWithCIImage:nil];

    // Fetch using GCD
    dispatch_queue_t downloadThumbnailQueue = dispatch_queue_create("Get Photo Thumbnail", NULL);
    dispatch_async(downloadThumbnailQueue, ^{
        UIImage *image = [self getTopPlacePhotoThumbnail:photo];
        dispatch_async(dispatch_get_main_queue(), ^{
            if ([self.tableView.visibleCells containsObject:cell]) {
                [cell.imageView setImage:image];
            }
        });
    });
    dispatch_release(downloadThumbnailQueue);

    return cell;
}

Перед нажатием строки Before clicking a row

После выбора строки After selecting the row

ОБНОВЛЕНИЕ: для тех, кого это интересует, это последний код, который я использовал:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  static NSString *CellIdentifier = @"Photo List Cell";
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

  // Configure the cell
  NSDictionary *photo = [self.photoList objectAtIndex:indexPath.row];

  if (photo != nil) {
    if ([[photo objectForKey:@"title"] length] > 0) {
        cell.textLabel.text = [photo objectForKey:@"title"];
    } else if ([[[photo objectForKey:@"description"] objectForKey:@"_content"] length] > 0) {
        cell.textLabel.text = [[photo objectForKey:@"description"] objectForKey:@"_content"];
    } else {
        cell.textLabel.text = @"Unknown";
    }
  }
  cell.imageView.image = [[UIImage alloc] initWithCIImage:nil];

  // Fetch using GCD
  dispatch_queue_t downloadThumbnailQueue = dispatch_queue_create("Get Photo Thumbnail", NULL);
  dispatch_async(downloadThumbnailQueue, ^{
    UIImage *image = [self getTopPlacePhotoThumbnail:photo];
    dispatch_async(dispatch_get_main_queue(), ^{
        UITableViewCell *cellToUpdate = [self.tableView cellForRowAtIndexPath:indexPath]; // create a copy of the cell to avoid keeping a strong pointer to "cell" since that one may have been reused by the time the block is ready to update it. 
        if (cellToUpdate != nil) {
            [cellToUpdate.imageView setImage:image];
            [cellToUpdate setNeedsLayout];
        }
    });
  });
  dispatch_release(downloadThumbnailQueue);

  return cell;
}

Ответы

Ответ 1

Вы должны вызвать setNeedsLayout в ячейке после установки миниатюры.

Из документа Apple Doc:

"Вызовите этот метод в главном потоке ваших приложений, если вы хотите настроить макет представлений представлений. Этот метод делает заметку о запросе и немедленно возвращается. Поскольку этот метод не вызывает немедленного обновления, но вместо этого ожидает следующий цикл обновления, вы можете использовать его для отмены макета нескольких представлений до обновления любого из этих представлений. Это позволяет вам консолидировать все ваши обновления макета в один цикл обновления, что обычно лучше для производительности".

Ответ 2

Другим решением (и тем, которое я использовал для этого назначения) является вызов setNeedsDisplay на изображении ячейки, как только вы присвоили UIImage этому изображению.

В принципе, я создал класс FlickrDownloader, который завершает работу FlickrFetcher (а также мой код кеширования). FlickrDownloader имеет этот метод:

(void)getImageForPhotoInfo:(NSDictionary *)photoInfo ofFormat:(FlickrPhotoFormat)format withCallback:(image_setter_callback_t)callback;

Это в основном вызов той же функции FlickrFetcher, но он добавляет обратный вызов, который вызывает завершение загрузки фотографии. В случае обновления imageView UITableViewCell обратный вызов выглядит следующим образом:

image_setter_callback_t setImage = ^(UIImage *image){
    cell.imageView.image = image;
    [cell.imageView setNeedsDisplay];
};