Представление прокрутки и представление таблицы при загрузке изображений с диска - PullRequest
6 голосов
/ 21 сентября 2010

Я пытаюсь повысить производительность моего приложения для iPhone с интенсивным использованием изображений, используя дисковый кэш изображений вместо того, чтобы работать по сети.Я смоделировал мой кэш изображений после SDImageCache (http://github.com/rs/SDWebImage/blob/master/SDImageCache.m), и почти такой же, но без асинхронных операций ввода / вывода кеша.

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

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

Я пытался исправить это следующим образом:

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

IsЕсть ли способ улучшить доступ к диску или уменьшить его влияние на пользовательский интерфейс?

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

Ниже приведены соответствующие фрагменты кода.Я не думаю, что я делаю что-то необычное или сумасшедшее.Эта задержка не выглядит заметной на iPhone 3G, но она довольно заметна на iPod Touch 2-го поколения.

Код кэширования изображения:

Вотсоответствующий фрагмент моего кода кэширования изображений.Довольно просто.

- (BOOL)hasImageDataForURL:(NSString *)url {
    return [[NSFileManager defaultManager] fileExistsAtPath:[self cacheFilePathForURL:url]];
}

- (NSData *)imageDataForURL:(NSString *)url {
    NSString *filePath = [self cacheFilePathForURL:url];

    // Set file last modification date to help enforce LRU caching policy
    NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
    [attributes setObject:[NSDate date] forKey:NSFileModificationDate];
    [[NSFileManager defaultManager] setAttributes:attributes ofItemAtPath:filePath error:NULL];

    return [NSData dataWithContentsOfFile:filePath];
}

- (void)storeImageData:(NSData *)data forURL:(NSString *)url {
    [[NSFileManager defaultManager] createFileAtPath:[self cacheFilePathForURL:url] contents:data attributes:nil];
}

Код контроллера представления прокрутки

Вот соответствующий фрагмент кода, который я использую для отображения изображений в моих контроллерах представления прокрутки.

- (void)scrollViewDidScroll:(UIScrollView *)theScrollView {
    CGFloat pageWidth = theScrollView.frame.size.width;
    NSUInteger index = floor((theScrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;

    [self loadImageFor:[NSNumber numberWithInt:index]];
    [self loadImageFor:[NSNumber numberWithInt:index + 1]];
    [self loadImageFor:[NSNumber numberWithInt:index - 1]];
}

- (void)loadImageFor:(NSNumber *)index {
    if ([index intValue] < 0 || [index intValue] >= [self.photoData count]) {
        return;
    }

    // ... initialize an image loader object that accesses the disk image cache or makes a network request

    UIView *iew = [self.views objectForKey:index];    
    UIImageView *imageView = (UIImageView *) [view viewWithTag:kImageViewTag];
    if (imageView.image == nil) {
        NSDictionary *photo = [self.photoData objectAtIndex:[index intValue]];
        [loader loadImage:[photo objectForKey:@"url"]];
    }
}

Объект загрузчика изображений - это просто легкий объект, который проверяет дисковый кэш и решает, извлекать ли изображение с диска или из сети.После этого он вызывает метод на контроллере представления прокрутки для отображения изображения:

- (void)imageLoadedFor:(NSNumber *)index image:(UIImage *)image {
    // Cache image in memory
    // ...

    UIView *view = [self.views objectForKey:index];
    UIImageView *imageView = (UIImageView *) [view viewWithTag:kImageViewTag];
    imageView.contentMode = UIViewContentModeScaleAspectFill;
    imageView.image = image;
}

ОБНОВЛЕНИЕ

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

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

Кроме того - я пробовал использовать маленькие изображения (миниатюры 50x50), и лаг, кажется, улучшается.Таким образом, кажется, что снижение производительности происходит из-за загрузки большого изображения с диска или загрузки большого изображения в объект UIImage.Я предполагаю, что одним из улучшений было бы уменьшение размера изображений, загружаемых в представления прокрутки и представления таблиц, что я и планировал сделать, тем не менее.Однако я просто не понимаю, как другие приложения, интенсивно использующие фотографии, могут отображать фотографии с высоким разрешением в виде с прокруткой без проблем с производительностью при переходе на диск или по сети.

Ответы [ 5 ]

1 голос
/ 05 апреля 2011

Вам следует проверить пример приложения LazyTableImages.

1 голос
/ 08 апреля 2011

Если вы сузили его до сетевой активности, я бы попробовал инкапсулировать ваш запрос, чтобы убедиться, что он на 100% меньше основного потока. Хотя вы можете использовать NSURLConnection асинхронно и отвечать на его методы делегата, я считаю, что проще обернуть синхронный запрос в фоновую операцию. Вы можете использовать NSOperation или грандиозную центральную диспетчеризацию, если ваши потребности более сложны. (Относительно) простой пример в реализации imageLoader может быть:

// imageLoader.m

// assumes that that imageCache uses kvp to look for images
- (UIImage *)imageForKey:(NSString *)key
{
    // check if we already have the image in memory
     UImage *image = [_images objectForKey:key];

    // if we don't have an image:
    // 1) start a background task to load an image from a file or URL
    // 2) return a default image to display while loading
    if (!image) {
        [self performSelectorInBackground:@selector(loadImageForKey) withObject:key];
        image = [self defaultImage];
    }

    return image;
}

- (void)loadImageForKey:(NSString *)key
{
    NSAutoReleasePool *pool = [[NSAutoReleasePool alloc] init];

    // attempt to load the image from the file cache
    UIImage *image = [self imageFromFileForKey:key];

    // if no image, load the image from the URL
    if (!image) {
        image = [self imageFromURLForKey:key];
    }

    // if no image, return default or imageNotFound image
    if (!image) {
        image = [self notFoundImage];
    }

    if ([_delegate respondsTo:@selector(imageLoader:didLoadImage:ForKey:)]) {
        [_delegate imageLoader:self didLoadImage:image forKey:key];
    }

    [pool release];
}

- (UIImage *)imageFromURLForKey:(NSString *)key
{
    NSError *error = nil;
    NSData *imageData = [NSData dataWithContentsOfURL:[self imageURLForKey:key]
                                              options:0
                                                error:&error];

    UIImage *image;

    // handle error if necessary
    if (error) {
        image = [self errorImage];
    }

    // create image from data
    else {
        image = [UIImage imageWithData:imageData];
    }

    return image;
}
0 голосов
/ 14 марта 2012

Обычно декодирование изображения занимает много времени, и это приводит к зависанию пользовательского интерфейса (поскольку все это происходит в основном потоке). Каждый раз, когда вы звоните [UIImage imageWithData:] на большом изображении, вы заметите сбой. Декодирование меньшего изображения происходит намного быстрее.

Два варианта:

  1. Сначала можно загрузить уменьшенную версию каждого изображения, а затем «повысить резкость» на didFinishScrolling. Миниатюры должны декодироваться достаточно быстро, чтобы не пропустить ни одного кадра.
  2. Вы можете загрузить уменьшенную версию каждого изображения или сначала показать индикатор загрузки, затем декодировать изображение с полным разрешением в другом потоке и добавить его, когда оно будет готово. (Это метод, который используется в приложении родных фотографий).

Я предпочитаю второй подход; с помощью GDC это достаточно просто.

0 голосов
/ 16 сентября 2011

У меня была эта проблема - вы достигли предела того, насколько быстро пользовательский интерфейс может загружать изображение при прокрутке, поэтому я бы просто обошел проблему и улучшил пользовательский опыт.

  1. Загружать изображения только тогда, когда прокрутка находится на новой «странице» (статический прямоугольник) и
  2. Поместите индикатор активности за прозрачным представлением прокрутки, чтобы обработать случай, когда пользователь прокручивает быстрее, чем приложение может загрузить контент
0 голосов
/ 01 декабря 2010

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

С уважением, Deepa

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...