UIImage解压缩导致滚动滞后

我有这个应用程序与全屏幕tableView显示一堆小图像。 这些图像从networking中拉出,在后台线程上处理,然后使用如下所示的方式保存到磁盘:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0); // code that adds some glosses, shadows, etc UIImage *output = UIGraphicsGetImageFromCurrentImageContext(); NSData* cacheData = UIImagePNGRepresentation(output); [cacheData writeToFile:thumbPath atomically:YES]; dispatch_async(dispatch_get_main_queue(), ^{ self.image = output; // refreshes the cell using KVO }); }); 

此代码仅在单元格显示的第一次执行(因为之后图像已经在磁盘上)。 在这种情况下,单元格使用以下方式加载:

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ UIImage *savedImage = [UIImage imageWithContentsOfFile:thumbPath]; if(savedImage) { dispatch_async(dispatch_get_main_queue(), ^{ self.image = savedImage; // refreshes the cell using KVO }); } }); 

我的问题是,在第一种情况下,滚动是黄油顺利。 但在第二种情况下(即直接从磁盘读取图像),滚动是超级生涩的,即使图像被加载。 绘图是什么造成了滞后。 使用仪器,我看到copyImageBlockSetPNGpng_read_nowpng_read_now占用了大部分的CPU(他们不是当分配self.imageUIGraphicsGetImageFromCurrentImageContext()

我假设发生这种情况是因为在第一种情况下,UIImage是绘图的原始输出,而在第二种情况下,每次绘制时都必须对PNG进行解压缩。 我尝试使用JPG而不是PNG,并得到类似的结果。

有没有办法来解决这个问题? 也许只有在第一次绘制PNG的时候才解压PNG呢?

你的问题是, +imageWithContentsOfFile:caching和延迟加载。 如果你想做这样的事情,而不是在你的背景排队使用这个代码:

 // Assuming ARC NSData* imageFileData = [[NSData alloc] initWithContentsOfFile:thumbPath]; UIImage* savedImage = [[UIImage alloc] initWithData:imageFileData]; // Dispatch back to main queue and set image... 

现在,使用这个代码,图像数据的实际解压缩仍然是懒惰的,并且花费了一点点,但是不像在代码示例中使用延迟加载时获得的文件访问次数那么多。

由于您仍然看到性能问题,您还可以强制UIImage在后台线程上解压缩图像:

 // Still on background, before dispatching to main UIGraphicsBeginImageContext(CGSizeMake(100, 100)); // this isn't that important since you just want UIImage to decompress the image data before switching back to main thread [savedImage drawAtPoint:CGPointZero]; UIGraphicsEndImageContext(); // dispatch back to main thread... 

杰森关于预先绘制图像以解压缩的提示是关键,但通过复制整个图像并丢弃原图,您将获得更好的性能。

在iOS上运行时创build的图像似乎比从文件中加载的图像更好地进行了优化,即使在强制解压之后也是如此。 出于这个原因,你应该像这样加载(将解压缩的图像放入一个NSCache也是一个好主意,所以你不必重新加载它):

 - (void)loadImageWithPath:(NSString *)path block:(void(^)(UIImage *image))block { static NSCache *cache = nil; if (!cache) { cache = [[NSCache alloc] init]; } //check cache first UIImage *image = [cache objectForKey:path]; if (image) { block(image); return; } //switch to background thread dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ //load image UIImage *image = [UIImage imageWithContentsOfFile:path]; //redraw image using device context UIGraphicsBeginImageContextWithOptions(image.size, YES, 0); [image drawAtPoint:CGPointZero]; image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); //back to main thread dispatch_async(dispatch_get_main_queue(), ^{ //cache the image [cache setObject:image forKey:path]; //return the image block(image); }); }); } 

另一种图像解压方式:

  NS_INLINE void forceImageDecompression(UIImage *image) { CGImageRef imageRef = [image CGImage]; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(NULL, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef), 8, CGImageGetWidth(imageRef) * 4, colorSpace,kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little); CGColorSpaceRelease(colorSpace); if (!context) { NSLog(@"Could not create context for image decompression"); return; } CGContextDrawImage(context, (CGRect){{0.0f, 0.0f}, {CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}}, imageRef); CFRelease(context); } 

使用:

  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ UIImage *image = [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%u.jpg", pageIndex]]; forceImageDecompression(image); dispatch_async(dispatch_get_main_queue(), ^{ [((UIImageView*)page)setImage:image]; }); }