我有一个全屏幕桌面应用程序,显示一堆微小的图像。这些图像从Web中提取,在后台线程上处理,然后使用以下内容保存到磁盘:
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
});
}
});
我的问题是,在第一种情况下,滚动是黄油平滑的。但在第二种情况下(它直接从磁盘读取图像),即使加载图像,滚动也是超级生涩。绘图是造成滞后的因素。使用工具,我看到copyImageBlockSetPNG
,png_read_now
和inflate
占用了大部分cpu(在将self.image
分配给UIGraphicsGetImageFromCurrentImageContext()
时不是这样)
我假设发生了这种情况,因为在第一种情况下,UIImage是绘图的原始输出,而在第二种情况下,它必须在每次绘制时解压缩PNG。我尝试使用JPG而不是PNG,我得到了类似的结果。
有没有办法解决这个问题?也许只有在它第一次被绘制时才解压缩PNG?
答案 0 :(得分:23)
您的问题是+imageWithContentsOfFile:
被缓存并延迟加载。如果你想做这样的事情,而是在你的后台队列中使用这个代码:
// 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...
答案 1 :(得分:16)
Jason关于预先绘制图像以解压缩它的提示是关键,但通过复制整个图像并丢弃原始图像,您将获得更好的性能。
在iOS上运行时创建的图像似乎比从文件加载的图像更好地优化了绘图,即使在您强制它们解压缩之后也是如此。出于这个原因,你应该像这样加载(将解压缩的图像放入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);
});
});
}
答案 2 :(得分:6)
图像解压缩的另一种方式:
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];
});
}