Какой лучший подход к кешированию асинхронного изображения на iPhone?

Я создаю приложение для iPhone, которое вытаскивает данные из веб-API, включая адреса электронной почты. Я хотел бы отобразить изображение, связанное с каждым адресом электронной почты в ячейках таблицы, поэтому я ищу адресную книгу для изображений и возвращаюсь по умолчанию, если адрес электронной почты не указан в книге. Это отлично работает, но у меня есть несколько проблем:

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

  • Ячейки таблицы. Поэтому я решил собрать все адреса электронной почты, для которых мне нужно найти изображения и найти их сразу. Таким образом, я повторяю книгу только один раз для всех адресов. Но это не работает для ячеек таблицы, где каждая ячейка соответствует одному адресу электронной почты. Я должен либо собрать все изображения перед отображением любых ячеек (потенциально медленными), либо каждую ячейку искать каждое изображение при загрузке (еще медленнее, так как мне нужно будет перебирать книгу, чтобы найти соответствие для каждого адрес электронной почты).

  • Асинхронный поиск. Итак, я подумал, что я посмотрю их навалом, но асинхронно, используя NSInvocationOperation. Для каждого изображения, найденного в AddressBook, я бы сохранил эскиз в песочнице приложения. Затем каждая ячейка может просто ссылаться на этот файл и показывать значение по умолчанию, если оно не существует (потому что оно отсутствует в книге или еще не найдено). Если впоследствии изображение будет обнаружено в асинхронном поиске, в следующий раз, когда изображение должно быть отображено, оно внезапно появится. Это может хорошо работать для периодической регенерации изображений (например, когда изображения были изменены в адресной книге). Но тогда для любого экземпляра моего приложения изображение может не отображаться на некоторое время.

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

Итак, мой вопрос: как это делают другие? Я играл с Tweetie2, и похоже, что он обновляет отображаемые ячейки таблицы асинхронно. Я предполагаю, что он отправляет отдельный HTTP-запрос для каждого изображения, которое ему нужно. Если это так, я полагаю, что поиск локальной адресной книги по электронной почте не менее эффективен, так что, возможно, лучший подход? Просто не беспокойтесь о проблемах с производительностью, связанных с поиском адресной книги?

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

Как вы все решаете эту проблему? Предложения будут высоко оценены!

Ответ 1

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

Создайте NSMutableDictionary, который будет служить вашим кешем в памяти для результатов поиска. Инициализируйте этот словарь с каждым адресом электронной почты из загрузки в качестве ключа, с часовым как это значение ключа (например, [NSNull null]).

Затем выполните итерацию через каждый ABRecordRef в адресной книге, вызвав ABRecordCopyValue(record, kABPersonEmailProperty) и перейдя по результатам в каждый возвращаемый ABMultiValue. Если какой-либо из адресов электронной почты является ключом в вашем кеше, установите [NSNumber numberWithInt:ABRecordGetRecordId(record)] в качестве значения этого ключа в словаре.

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

Ответ 2

Я бы использовал последний выбранный вами метод (Asynchronous Cell Cell Lookup), но только просмотр изображений для текущих отображаемых записей. Я перегружаю методы UIScrollViewDelegate, чтобы узнать, когда пользователь остановил прокрутку, а затем только начинает делать запросы для текущих видимых ячеек.

Что-то вроде этого (это немного изменено из учебника, который я нашел в Интернете, которого я не могу найти сейчас, извинения за не цитирование автора):

- (void)loadContentForVisibleCells
{
    NSArray *cells = [self.table visibleCells];
    [cells retain];
    for (int i = 0; i < [cells count]; i++) 
    { 
        // Go through each cell in the array and call its loadContent method if it responds to it.
        AddressRecordTableCell *addressTableCell = (AddressRecordTableCell *)[[cells objectAtIndex: i] retain];
        [addressTableCell loadImage];
        [addressTableCell release];
        addressTableCell = nil;
    }
    [cells release];
}


- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; 
{
    // Method is called when the decelerating comes to a stop.
    // Pass visible cells to the cell loading function. If possible change 
    // scrollView to a pointer to your table cell to avoid compiler warnings
    [self loadContentForVisibleCells]; 
}


- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
{
    if (!decelerate) 
    {
       [self loadContentForVisibleCells]; 
    }
}

Как только вы узнаете, какие записи в настоящее время видны, просто поиск этих (возможно, 5 -7 записей) будет молниеносно. Как только вы возьмете изображение, просто кешируйте его в словаре, чтобы вам не пришлось повторно повторять запрос для изображения позже.

Ответ 4

FYI, я выпустил бесплатную, мощную и легкую библиотеку для выполнения асинхронной загрузки изображений и быстрого кэширования файлов: управляемые объекты HJ http://www.markj.net/asynchronous-loading-caching-images-iphone-hjobjman/