Подтвердить что ты не робот

Оптимизированная загрузка изображений в UIScrollView

У меня есть UIScrollView, у которого есть набор изображений, загруженных бок о бок внутри него. Здесь вы можете увидеть пример моего приложения: http://www.42restaurants.com. Моя проблема связана с использованием памяти. Я хочу ленить загружать изображения, поскольку они собираются появиться на экране, и выгрузить изображения, которые не отображаются на экране. Как вы можете видеть в коде, я разрабатываю как минимум, какое изображение мне нужно загрузить, а затем назначить часть загрузки NSOperation и поместить его в NSOperationQueue. Все отлично работает, несмотря на резкий прокрутка.

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

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    [self manageThumbs];    
}

- (void) manageThumbs{
    int centerIndex = [self centerThumbIndex];
    if(lastCenterIndex == centerIndex){
        return;
    }

    if(centerIndex >= totalThumbs){
        return;
    }

    NSRange unloadRange;
    NSRange loadRange;

    int totalChange = lastCenterIndex - centerIndex;
    if(totalChange > 0){ //scrolling backwards
        loadRange.length = fabsf(totalChange);
        loadRange.location = centerIndex - 5;
        unloadRange.length = fabsf(totalChange);
        unloadRange.location = centerIndex + 6;
    }else if(totalChange < 0){ //scrolling forwards
        unloadRange.length = fabsf(totalChange);
        unloadRange.location = centerIndex - 6;
        loadRange.length = fabsf(totalChange);
        loadRange.location = centerIndex + 5;
    }
    [self unloadImages:unloadRange];
    [self loadImages:loadRange];
    lastCenterIndex = centerIndex;

    return;
}

- (void) unloadImages:(NSRange)range{
    UIScrollView *scrollView = (UIScrollView *)[[self.view subviews] objectAtIndex:0];
    for(int i = 0; i < range.length && range.location + i < [scrollView.subviews count]; i++){
        UIView *subview = [scrollView.subviews objectAtIndex:(range.location + i)];
        if(subview != nil && [subview isKindOfClass:[ThumbnailView class]]){
            ThumbnailView *thumbView = (ThumbnailView *)subview;
            if(thumbView.loaded){
                UnloadImageOperation *unloadOperation = [[UnloadImageOperation alloc] initWithOperableImage:thumbView];
                [queue addOperation:unloadOperation];
                [unloadOperation release];
            }
        }
    }
}

- (void) loadImages:(NSRange)range{
    UIScrollView *scrollView = (UIScrollView *)[[self.view subviews] objectAtIndex:0];
    for(int i = 0; i < range.length && range.location + i < [scrollView.subviews count]; i++){
        UIView *subview = [scrollView.subviews objectAtIndex:(range.location + i)];
        if(subview != nil && [subview isKindOfClass:[ThumbnailView class]]){
            ThumbnailView *thumbView = (ThumbnailView *)subview;
            if(!thumbView.loaded){
                LoadImageOperation *loadOperation = [[LoadImageOperation alloc] initWithOperableImage:thumbView];
                [queue addOperation:loadOperation];
                [loadOperation release];
            }
        }
    }
}

EDIT: Спасибо за отличные ответы. Вот мой код NSOperation и ThumbnailView. Я пробовал пару вещей за выходные, но мне удалось улучшить производительность, приостановив очередь операций во время прокрутки и возобновив ее при завершении прокрутки.

Вот мои фрагменты кода:

//In the init method
queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:4];


//In the thumbnail view the loadImage and unloadImage methods
- (void) loadImage{
    if(!loaded){
        NSString *filename = [NSString stringWithFormat:@"%03d-cover-front", recipe.identifier, recipe.identifier]; 
        NSString *directory = [NSString stringWithFormat:@"RestaurantContent/%03d", recipe.identifier];     

        NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:@"png" inDirectory:directory];
        UIImage *image = [UIImage imageWithContentsOfFile:path];

        imageView = [[ImageView alloc] initWithImage:image andFrame:CGRectMake(0.0f, 0.0f, 176.0f, 262.0f)];
        [self addSubview:imageView];
        [self sendSubviewToBack:imageView];
        [imageView release];
        loaded = YES;       
    }
}

- (void) unloadImage{
    if(loaded){
        [imageView removeFromSuperview];
        imageView = nil;
        loaded = NO;
    }
}

Затем мои операции загрузки и выгрузки:

- (id) initWithOperableImage:(id<OperableImage>) anOperableImage{

    self = [super init];
    if (self != nil) {
        self.image = anOperableImage;
    }
    return self;
}

//This is the main method in the load image operation
- (void)main {
    [image loadImage];
}


//This is the main method in the unload image operation
- (void)main {
    [image unloadImage];
}
4b9b3361

Ответ 1

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

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

Несколько элементов для рассмотрения:

  • Попробуйте загрузить ThumbnailView с одним изображением в начале и отключить очередь NSOperation (просто пропустите все, что указано после проверки "если загружено" ). Это даст вам немедленное представление о том, влияет ли код NSOperation на производительность.

  • Имейте в виду, что -scrollViewDidScroll: может встречаться много раз в течение одного действия прокрутки. В зависимости от того, как выполняется перемещение прокрутки и как выполняется ваш -centerThumbIndex, вы можете пытаться поочередно выполнять одни и те же действия. Если вы учли это в своем -initWithOperableImage или -loaded, тогда его возможный код здесь вызывает проблемы с синхронизацией/блокировкой (см. 3 ниже). Вы должны отслеживать, было ли запущено NSOperation с использованием свойства "атом" в экземпляре ThumbnailView. Предотвратите очередность другой операции, если это свойство установлено, и только отмените это свойство (вместе с загруженным) в конце процессов NSOperation.

  • Так как NSOperationQueue работает в своем потоке, убедитесь, что ни один из исполняемых вами кода в NSOperation не синхронизируется или не блокируется в основном потоке. Это устранит все преимущества использования NSOperationQueue.

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

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

  • Убедитесь, что операция загрузки загружает только "предварительно масштабированную" миниатюру, а не загрузку полноразмерного изображения и масштабирование или обработку. Это было бы большим количеством дополнительных накладных расходов в середине действия прокрутки. Идите еще дальше и убедитесь, что вы преобразовали их в PNG16 без альфа-канала. Это даст по крайней мере уменьшение (4: 1) в размерах, с надеждой не обнаруживаемое изменение визуального изображения. Также рассмотрите использование изображений формата PVRTC, которые еще больше уменьшат размер (уменьшение 8: 1). Это значительно сократит время, необходимое для чтения изображений с диска.

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

Я бы порекомендовал опубликовать ваш код NSOperation для загрузки и выгрузки и, по крайней мере, достаточно ThumbnailView, чтобы понять, как он взаимодействует с экземплярами NSOperation.

Надеюсь, это помогло в некоторой степени,

Барни

Ответ 2

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

Установите флаг для отключения загрузки изображения в:

-scrollViewWillBeginDragging:

Повторно включите загрузку изображений, когда прокрутка прекращается с помощью:

-scrollViewDidEndDragging:willDecelerate:

UIScrollViewDelegate. Когда параметр willDecelerate: NO, движение остановилось.

Ответ 3

проблема здесь:

 UIImage *image = [UIImage imageWithContentsOfFile:path];

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

Ответ 4

При исследовании этой проблемы я нашел еще два ресурса, которые могут представлять интерес:

Посмотрите пример проекта iPhone "PageControl": http://developer.apple.com/iphone/library/samplecode/PageControl/index.html

Это ленивые диспетчеры просмотра загрузок в UIScrollView.

  • и -

Ознакомьтесь с cocoa touch lib: http://github.com/facebook/three20, который имеет класс TTPhotoViewController, который ленив загружает фотографии/миниатюры из Интернета/диск.

Ответ 5

Задайте значение isRasterize = YES для добавления дополнительного содержимого в список прокрутки. Считается, что удаление отвратительного поведения настраиваемого вида прокрутки напоминает шарм.:)

Также выполните некоторые профилирования с использованием инструментов в Xcode. Пройдите через учебные пособия, созданные для профилирования Ray Wenderlich, это очень помогло мне.