如何提高包含大量小图像的UCollectionView的性能?

Ras*_*sto 17 memory performance image ios uicollectionview

在我的iOS应用程序中,我UICollectionView显示了大约1200个小(35x35点)图像.图像存储在应用程序包中.

我正确地重用了UICollectionViewCells但仍然存在性能问题,这取决于我如何处理图像加载:

  • 我的应用程序是应用程序扩展,那些内存有限(在这种情况下为40 MB).将所有1200个图像放入Assets目录并使用它们加载它们会UIImage(named: "imageName")导致内存崩溃 - 系统缓存的图像会填满内存.在某些时候,应用程序需要分配更大的内存部分,但由于缓存的图像,这些内存不可用.操作系统刚刚杀死了应用程序,而不是触发内存警告和清理缓存.

  • 我改变了方法以避免图像缓存.我将图像作为png文件放到我的项目中(而不是作为asssets目录),我现在正在使用它们加载它们NSBundle.mainBundle().pathForResource("imageName", ofType: "png").该应用程序不再因内存错误而崩溃,但单个图像的加载需要更长时间,即使在最新的iPhone上,快速滚动也会滞后.

我对图像有完全的控制,可以将它们转换为.jpeg或优化它们(我已经尝试过ImageOptim和其他一些选项而没有成功).

如何立即解决这两个性能问题?


编辑1:

我也尝试在后台线程中加载图像.这是我的子类的代码UICollectionViewCell:

private func loadImageNamed(name: String) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { [weak self] () in
        let image = bundle.pathForResource(name, ofType: "png")?.CGImage
        if name == self?.displayedImageName {
            dispatch_async(dispatch_get_main_queue(), {
                if name == self?.displayedImageName {
                    self?.contentView.layer.contents = image
                }
            })
        }
    })
}
Run Code Online (Sandbox Code Playgroud)

这使得滚动平滑而不消耗额外的内存用于缓存,但是当以编程方式滚动到某个位置时(例如,当UICollectionView滚动到顶部时)它会导致另一个问题:在滚动动画期间,图像不会更新(滚动速度太快,无法加载)和之后滚动完成它需要错误的图像显示几分之一 - 并且一个接一个地用正确的图像替换.这在视觉上非常令人不安.


编辑2:

我无法将小图像分组为更大的合成图像,并按照此答案的建议显示.

原因:

  • 考虑不同的屏幕尺寸和方向.必须为每个人提供预先组合的图像,这将使应用程序下载量巨大.
  • 小图像可以按不同顺序显示,其中一些可能在某些情况下隐藏.我完全不能为每种可能的组合和订单预先制作图像.

Vla*_*pko 5

我可以提出可能解决您问题的替代方法:
考虑将图像块渲染为单个合成图像.这样大的图像应该覆盖app窗口的大小.对于用户来说,它看起来像是小图像的集合,但从技术上讲它将是大图像的表格.

你当前的布局:

 |      |      |      |
 | cell | cell | cell |  -> cells outside of screen
 |      |      |      |
************************
*|      |      |      |*
*| cell | cell | cell |* -> cells displayed on screen
*|      |      |      |*
*----------------------*
*|      |      |      |* 
*| cell | cell | cell |* -> cells displayed on screen
*|      |      |      |*
*----------------------*
*|      |      |      |*
*| cell | cell | cell |* -> cells displayed on screen
*|      |      |      |*
************************
 |      |      |      |
 | cell | cell | cell |  -> cells outside of screen
 |      |      |      |
Run Code Online (Sandbox Code Playgroud)

拟议布局:

 |                    |
 |     cell with      |
 |   composed image   |  -> cell outside of screen
 |                    |
************************
*|                    |*
*|                    |*
*|                    |* 
*|                    |* 
*|     cell with      |*
*|   composed image   |* -> cell displayed on screen
*|                    |*
*|                    |*
*|                    |* 
*|                    |* 
*|                    |*
************************
 |                    |
 |     cell with      |
 |   composed image   |  -> cell outside of screen
 |                    |
Run Code Online (Sandbox Code Playgroud)

理想情况下,如果您预先渲染这样的合成图像并在构建时将它们放到项目中,但您也可以在运行时渲染它们.当然,第一个变体的工作速度要快得多.但无论如何,单个大图像会花费更少的内存,然后分离该图像.

如果您有可能预先渲染它们,则使用JPEG格式.在这种情况下,您的第一个解决方案([UIImage imageNamed:]在主线程上加载图像)可能会运行良好,因为使用的内存更少,布局更简单.

如果你必须在运行时渲染它们,那么你将需要使用当前的解决方案(在后台工作),当你快速动画发生时你仍然会看到图像错位,但在这种情况下它将是单个错位(一个图像覆盖窗口)框架),所以看起来应该更好.

如果您需要知道用户点击了什么图像(原始小图像35x35),您可以使用UITapGestureRecognizer附加到单元格.识别手势时,您可以使用locationInView:方法计算小图像的正确索引.

我不能说它100%解决了你的问题,但尝试是有意义的.


小智 5

  1. 每次单元格出现时,都无需从文档目录中获取图像。
  2. 提取图像后,您可以将其保存在NSCache中,下次只需从NSCache获取图像,而不必再次从文档目录中提取图像。
  3. 为NSCache objCache创建一个对象;
  4. 在您的cellForItemAtIndexPath中,只需写下

    UIImage *cachedImage = [objCache objectForKey:arr_PathFromDocumentDirectory[indexPath.row]];
    
    if (cachedImage) {
        imgView_Cell.image = cachedImage;
    } else {
        dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
        dispatch_async(q, ^{
            /* Fetch the image from the Document directory... */
            [self downloadImageWithURL:arr_PathFromDocument[indexPath.row] completionBlock:^(BOOL succeeded, CGImageRef data, NSString* path) {
                if (succeeded) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        UIImage *img =[UIImage imageWithCGImage:data];
                        imgView_Cell.image = img;
                        [objCache setObject:img forKey::arr_PathFromDocument[indexPath.row]];
                    });
                }
            }];
        });
    }
    
    Run Code Online (Sandbox Code Playgroud)
  5. 提取图像后,请在NSCache中使用路径进行设置。下次它将检查是否已下载,然后仅从缓存中进行设置。

如果您需要任何帮助,请告诉我。

谢谢!


Kev*_*vin 3

从 PNG 更改为 JPEG 无助于节省内存,因为当您将图像从文件加载到内存时,它会从压缩数据中提取到未压缩字节。

对于性能问题,我建议您异步加载图像并使用委托/块更新视图。并在内存中保留一些图像(但不是全部,假设 100 个)

希望这可以帮助!