You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS中如何缓存CIImage转换后的UIImage以优化UICollectionView性能?

解决UICollectionView中CIImage重复渲染卡顿的问题

这个问题我太熟悉了——你用[UIImage imageWithCIImage:]生成的UIImage其实只是CIImage的一个“包装器”,并没有提前完成像素级的渲染计算。只有当UIImage被显示到屏幕上时,系统才会调用默认的CIContext去实时渲染CIImage,这就导致每个单元格出现时都要重新跑一遍滤镜计算,性能自然拉胯。

核心解决方案:主动用CIContext渲染成位图

要强制完成CIImage的计算并缓存,关键是用CIContext把CIImage主动渲染成CGImage,再转成UIImage。这样得到的UIImage存储的是已经计算好的位图数据,复用的时候不会再触发渲染。

步骤1:复用CIContext(避免重复创建开销)

CIContext是渲染CIImage的核心对象,创建它的开销不小,所以最好在类中初始化一个共享实例:

// 在你的ViewController的.h或.m中声明
@property (nonatomic, strong) CIContext *ciRenderContext;

// 在viewDidLoad或初始化方法中创建
- (void)viewDidLoad {
    [super viewDidLoad];
    // 用GPU加速渲染(默认就是GPU,这里显式声明更清晰)
    self.ciRenderContext = [CIContext contextWithOptions:@{
        kCIContextUseSoftwareRenderer : @NO,
        kCIContextPriorityRequestLow : @YES // 如果渲染优先级不高,可以加这个优化
    }];
}

步骤2:提前渲染并缓存UIImage

修改你的缓存生成代码,用CIContext主动渲染CIImage为CGImage,再转成UIImage:

NSMutableArray *filterPreviews = [NSMutableArray array];
for (int i=0;i<10;i++) {
    CIFilter *filteredImage = <你的滤镜生成逻辑>;
    CIImage *ciImg = filteredImage.outputImage;
    
    // 关键:主动渲染CIImage到CGImage
    CGImageRef cgRenderedImg = [self.ciRenderContext createCGImage:ciImg fromRect:ciImg.extent];
    // 转成UIImage(此时UIImage存储的是位图数据)
    UIImage *cachedImg = [UIImage imageWithCGImage:cgRenderedImg];
    // 记得释放CGImageRef,避免内存泄漏
    CGImageRelease(cgRenderedImg);
    
    [filterPreviews addObject:cachedImg];
}
// 把缓存数组存起来
self.filterPreviews = filterPreviews;

步骤3:单元格复用缓存图片

这部分你的代码可以保持不变,因为现在的filterPreviews里都是已经渲染好的位图UIImage,设置给UIImageView时不会再触发计算:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    colorFilterCollectionViewCell *cell = (colorFilterCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"cellIdentifier" forIndexPath:indexPath];
    cell.filterPreview.image = self.filterPreviews[indexPath.row];
    return cell;
}

额外优化建议

  1. 提前缩放到目标尺寸:如果你的单元格预览图尺寸固定,建议在渲染前把CIImage裁剪/缩放到单元格的大小,减少渲染的像素数量,进一步提升性能:
// 假设单元格预览图的尺寸是80x80
CGRect targetExtent = CGRectMake(0, 0, 80, 80);
CIImage *scaledCIImg = [ciImg imageByApplyingTransform:CGAffineTransformMakeScale(
    targetExtent.size.width / ciImg.extent.size.width,
    targetExtent.size.height / ciImg.extent.size.height
)];
// 再用scaledCIImg去渲染CGImage
  1. 后台线程渲染:如果滤镜数量多、计算量大,建议把渲染逻辑放到后台线程执行,避免阻塞主线程导致界面卡顿:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSMutableArray *tempCachedImages = [NSMutableArray array];
    for (int i=0;i<10;i++) {
        // 这里放滤镜生成和渲染逻辑
        CIFilter *filteredImage = <你的滤镜生成逻辑>;
        CIImage *ciImg = filteredImage.outputImage;
        CGImageRef cgRenderedImg = [self.ciRenderContext createCGImage:ciImg fromRect:ciImg.extent];
        UIImage *cachedImg = [UIImage imageWithCGImage:cgRenderedImg];
        CGImageRelease(cgRenderedImg);
        [tempCachedImages addObject:cachedImg];
    }
    // 回到主线程更新缓存并刷新列表
    dispatch_async(dispatch_get_main_queue(), ^{
        self.filterPreviews = tempCachedImages;
        [self.collectionView reloadData];
    });
});

为什么之前的CGImage/NSData尝试无效?

你之前可能是直接从CIImage转CGImage但没用到CIContext——CIImage本身只是一个“计算指令集”,必须通过CIContext才能转换成实际的位图数据。如果只是用UIImagePNGRepresentation这类方法,本质上还是会触发延迟渲染,所以达不到缓存的效果。

内容的提问来源于stack exchange,提问作者Laurent Crivello

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 03:32:27