7

У меня есть UICollectionView в моем приложении, и каждая ячейка представляет собой UIImageView и некоторые текстовые метки. Проблема в том, что когда у меня есть UIImageViews, отображающий их изображения, производительность прокрутки ужасная. Это не так гладко, как прокрутка опыта UITableView или даже того же UICollectionView без UIImageView.Плохая производительность прокрутки UICollectionView с UIImage

Я нашел this question несколько месяцев назад, и кажется, что ответ был найден, но он написан в RubyMotion, и я этого не понимаю. Я попытался понять, как преобразовать его в Xcode, но так как я никогда не использовал NSCache, это немного сложно. Плакат там также указал на here о внедрении чего-то в дополнение к их решению, но я не уверен, где поставить этот код. Возможно потому, что я не понимаю код из first question.

Может ли кто-нибудь помочь в переводе на Xcode?

def viewDidLoad 
    ... 
    @images_cache = NSCache.alloc.init 
    @image_loading_queue = NSOperationQueue.alloc.init 
    @image_loading_queue.maxConcurrentOperationCount = 3 
    ... 
end 

def collectionView(collection_view, cellForItemAtIndexPath: index_path) 
    cell = collection_view.dequeueReusableCellWithReuseIdentifier(CELL_IDENTIFIER, forIndexPath: index_path) 
    image_path = @image_paths[index_path.row] 

    if cached_image = @images_cache.objectForKey(image_path) 
    cell.image = cached_image 
    else 
    @operation = NSBlockOperation.blockOperationWithBlock lambda { 
     @image = UIImage.imageWithContentsOfFile(image_path) 
     Dispatch::Queue.main.async do 
     return unless collectionView.indexPathsForVisibleItems.containsObject(index_path) 
     @images_cache.setObject(@image, forKey: image_path) 
     cell = collectionView.cellForItemAtIndexPath(index_path) 
     cell.image = @image 
     end 
    } 
    @image_loading_queue.addOperation(@operation) 
    end 
end 

Вот код из second question, что спрашивающий из first question сказал, решил проблему:

UIImage *productImage = [[UIImage alloc] initWithContentsOfFile:path]; 

CGSize imageSize = productImage.size; 
UIGraphicsBeginImageContext(imageSize); 
[productImage drawInRect:CGRectMake(0, 0, imageSize.width, imageSize.height)]; 
productImage = UIGraphicsGetImageFromCurrentImageContext(); 
UIGraphicsEndImageContext(); 

Опять же, я не знаю, как/где осуществить это.

Большое спасибо.

+0

Оставьте свой код! Что вы пробовали? Почему производительность вашего приложения медленная? Каков результат профилирования, чтобы увидеть, что происходит? –

+0

Это не мое приложение, которое имеет медленную производительность, оно только прокручивает ячейки в UICollectionView. Остальная часть приложения работает очень хорошо. В настоящее время я использую 'cell.cardImageView.image = [UIImage imageWithData: [NSData dataWithContentsOfFile: tempCard]];' для установки изображения. Используемое изображение не является частью пакета приложений, но обычно загружается и сохраняется в папке Caches. – Nick

ответ

19

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

// 
// ImageRequest.h 

// This class keeps track of in-flight instances, creating only one NSURLConnection for 
// multiple matching requests (requests with matching URLs). It also uses NSCache to cache 
// retrieved images. Set the cache count limit with the macro in this file. 

#define kIMAGE_REQUEST_CACHE_LIMIT 100 
typedef void (^CompletionBlock) (UIImage *, NSError *); 

@interface ImageRequest : NSMutableURLRequest 

- (UIImage *)cachedResult; 
- (void)startWithCompletion:(CompletionBlock)completion; 

@end 

// 
// ImageRequest.m 

#import "ImageRequest.h" 

NSMutableDictionary *_inflight; 
NSCache *_imageCache; 

@implementation ImageRequest 

- (NSMutableDictionary *)inflight { 

    if (!_inflight) { 
     _inflight = [NSMutableDictionary dictionary]; 
    } 
    return _inflight; 
} 

- (NSCache *)imageCache { 

    if (!_imageCache) { 
     _imageCache = [[NSCache alloc] init]; 
     _imageCache.countLimit = kIMAGE_REQUEST_CACHE_LIMIT; 
    } 
    return _imageCache; 
} 

- (UIImage *)cachedResult { 

    return [self.imageCache objectForKey:self]; 
} 

- (void)startWithCompletion:(CompletionBlock)completion { 

    UIImage *image = [self cachedResult]; 
    if (image) return completion(image, nil); 

    NSMutableArray *inflightCompletionBlocks = [self.inflight objectForKey:self]; 
    if (inflightCompletionBlocks) { 
     // a matching request is in flight, keep the completion block to run when we're finished 
     [inflightCompletionBlocks addObject:completion]; 
    } else { 
     [self.inflight setObject:[NSMutableArray arrayWithObject:completion] forKey:self]; 

     [NSURLConnection sendAsynchronousRequest:self queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { 
      if (!error) { 
       // build an image, cache the result and run completion blocks for this request 
       UIImage *image = [UIImage imageWithData:data]; 
       [self.imageCache setObject:image forKey:self]; 

       id value = [self.inflight objectForKey:self]; 
       [self.inflight removeObjectForKey:self]; 

       for (CompletionBlock block in (NSMutableArray *)value) { 
        block(image, nil); 
       } 
      } else { 
       [self.inflight removeObjectForKey:self]; 
       completion(nil, error); 
      } 
     }]; 
    } 
} 

@end 

Теперь клетка (сбор или таблица) обновление довольно просто:

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 

    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath]; 

    NSURL *url = [NSURL URLWithString:@"http:// some url from your model"]; 
    // note that this can be a web url or file url 

    ImageRequest *request = [[ImageRequest alloc] initWithURL:url]; 

    UIImage *image = [request cachedResult]; 
    if (image) { 
     UIImageView *imageView = (UIImageView *)[cell viewWithTag:127]; 
     imageView.image = image; 
    } else { 
     [request startWithCompletion:^(UIImage *image, NSError *error) { 
      if (image && [[collectionView indexPathsForVisibleItems] containsObject:indexPath]) { 
       [collectionView reloadItemsAtIndexPaths:@[indexPath]]; 
      } 
     }]; 
    } 
    return cell; 
} 
+0

Спасибо за это. Я попытался поместить его в свое приложение, и 'url' всегда подходит« nil ». Любая мысль? – Nick

+0

Вы заменили эту ужасную строку, которую я использовал в качестве примера с реальным веб-адресом? – danh

+0

Да, я его заменил. Однако я использую локальный URL-адрес, указывающий на каталог Caches, а не на веб-адрес. Это не проблема, правда? Core Data использует 'NSURL', чтобы указать локальный магазин приложения. – Nick

0

Я имел вопросы о UICollectionView прокрутки.

Что сработало (почти) как очарование для меня: я заполнил ячейки с помощью png thumbnails 90x90. Я говорю почти потому, что первый полный свиток не настолько гладкий, но никогда больше не разбился.

В моем случае размер ячейки 90x90.

Раньше у меня было много оригинальных размеров png, и это было очень неряшливо, когда первоначальный размер png был больше ~ 1000x1000 (много сбоев при первом прокрутке).

Итак, я выбираю 90x90 (или т.п.) на UICollectionView и отображаю оригинальные png (независимо от размера). надеюсь, что это может помочь другим.

5

В общем случае плохое поведение прокрутки для UICollectionViews или UITableViews происходит из-за того, что ячейки удалены и сконструированы в основном потоке iOS. Существует мало свободы для подготовки клеток или их создания в фоновом потоке, вместо этого они выгружаются и создаются по мере того, как вы прокручиваете блокировку пользовательского интерфейса. (Лично я считаю этот плохой дизайн Apple все, хотя он упрощает вопросы, потому что вам не нужно знать о потенциальных проблемах с потоками. Я думаю, что они должны были сделать крючок, хотя для обеспечения пользовательской реализации пула UICollectionViewCell/UITableViewCell, который может обрабатывать удаление/повторное использование ячеек.)

Наиболее важные причины для снижения производительности действительно связаны с данными изображения и (в порядке убывания величины) в моем опыте:

  • Синхронные вызовы для загрузки данных изображения: всегда делать это асинхронно и вызов [UIImageView setImage:] с построенным изображением при готовности в основном потоке
  • Синхронные вызовы для создания изображений из данных локальной файловой системы или из других сериализованных данных: выполняйте это также асинхронно. (например, [UIImage imageWithContentsOfFile:], [UIImage imageWithData:] и т. д.).
  • Вызов [UIImage imageNamed:]: при первом загрузке этого изображения он подается из файловой системы. Вы можете захотеть предварительно очистить изображения (просто загрузив [UIImage imageNamed:] до того, как ячейка будет построена так, чтобы они могли быть немедленно отправлены из памяти.
  • Вызов [UIImageView setImage:] не самый быстрый способ, но может часто не следует избегать, если вы не используете статические изображения. Для статических изображений иногда быстрее использовать разные виды изображений, которые вы устанавливаете скрытыми или нет, в зависимости от того, должны ли они отображаться вместо изменения изображения на одном и том же изображении.
  • В первый раз, когда ячейка удаляется, она либо загружается из Nib, либо сконструирована с помощью alloc-init, и устанавливается некоторая начальная компоновка или свойства (возможно, также изображения, если вы их использовали). Это приводит к плохому прокручиванию при первом использовании ячейки.

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

Смежные вопросы