-->

可怜UICollectionView滚动性能有了的UIImage(Poor UICollection

2019-08-19 07:52发布

我有一个UICollectionView在我的应用程序,并且每个单元是一个UIImageView和一些文字标签。 问题是,当我有自己的显示图像的UIImageViews,滚动性能是可怕的。 这是远不一样光滑如一个UITableView的滚动体验,甚至在同一UICollectionView没有的UIImageView。

我发现这个问题,以前从几个月了,这似乎是一个答案被发现,但它写在RubyMotion,我不明白这一点。 我想看看如何将其转换为Xcode的,但因为我从来没有使用NSCache要么,这是一个有点难以。 海报上有还指出了这里关于实施,除了他们的解决方案的东西,但我不知道在哪里把这些代码要么。 可能是因为我不明白,从代码的第一个问题 。

会有人能够帮助翻译到这一点的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

下面是从代码的第二个问题是,的提问者的第一个问题所说的解决了这个问题:

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

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

同样,我不知道如何/在哪里实现这一点。

非常感谢。

Answer 1:

下面是我遵循的模式。 总是加载非同步和缓存结果。 让没有关于当非同步加载完成视图的状态估计。 我有一个简化的负荷如下:a类:

//
//  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;
}


Answer 2:

在UICollectionViews或UITableViews一般不良滚动行为发生,因为细胞被出队,并在内部监督办公室主线程构建。 很少有自由预先缓存细胞或构建它们在后台线程,而不是他们队并为您滚动阻塞UI构建。 (我个人被苹果发现这个糟糕的设计都尽管它简化问题,因为你没有意识到潜在的线程问题,我想他们应该给一个钩子虽然提供了UICollectionViewCell / UITableViewCell的池自定义实现其可以处理单元的出队/复用)。

性能下降最重要的原因确实与图像数据和(在减少数量级)是我的经验:

  • 同步调用下载图像数据:总是这样做异步,并调用[UIImageView的setImage:]与构建的图像时,在主线程准备
  • 同步调用本地文件系统上构建从数据的图像,或者其它串行数据:这样做异步为好。 (例如,[UIImage的imageWithContentsOfFile:],[UIImage的imageWithData:]等)。
  • 调用[UIImage的imageNamed:]:第一次遇到这种图像加载它从文件系统服务。 您可能需要预先缓存图像(只是通过加载[UIImage的imageNamed:]小区之前实际上是构造,使得它们可以从内存中直接提供,而不是。
  • 调用[的UIImageView setImage:]是不是最快的方法要么,但通常无法避免,除非你使用静态图片。 对于静态图像,有时更快地使用你设置为不同的图像视图中隐藏与否取决于他们是否应显示,而不是在同一图像视图改变图像。
  • 一个小区被出队它第一次或者从笔尖加载或与ALLOC-init和一些初始布局构造或(如果使用它们可能也图像)属性被设置。 这会导致糟糕的滚动行为第一次使用电池。

因为我对平滑滚动非常挑剔(即使它仅在第一次使用电池)我构建了一个整体框架,以预先缓存通过继承UINib细胞(这基本上是你进入了内部监督办公室使用的离队过程中,只有挂钩)。 但是,这可能超出你的需求。



Answer 3:

我对问题UICollectionView滚动。

什么工作(几乎)对我来说就像魅力:我填充PNG缩略图90x90细胞。 我说的差不多,因为第一个完整的滚动不是那么顺利,但永不死机了。

在我的情况下,单元尺寸为90x90。

我以前有许多原始PNG大小,这是非常不连贯,当原来的png格式大小比1000×1000〜(在第一滚动许多崩溃)更大。

所以,我选择在90x90(或类似) UICollectionView并显示原始PNG的(无论大小)。 希望这可以帮助别人。



文章来源: Poor UICollectionView Scrolling Performance With UIImage