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; }
额外优化建议
- 提前缩放到目标尺寸:如果你的单元格预览图尺寸固定,建议在渲染前把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
- 后台线程渲染:如果滤镜数量多、计算量大,建议把渲染逻辑放到后台线程执行,避免阻塞主线程导致界面卡顿:
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
相关产品推荐
相关产品推荐

