UIImage解压缩导致滚动滞后

sam*_*tte 29 iphone core-graphics ios

我有这个应用程序与全屏tableView显示一堆微小的图像.这些图像从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
    });
});
Run Code Online (Sandbox Code Playgroud)

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

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
        });
    }
});
Run Code Online (Sandbox Code Playgroud)

我的问题是,在第一种情况下,滚动是黄油平滑.但在第二种情况下(它直接从磁盘读取图像),即使加载图像,滚动也是超级生涩.绘图是造成滞后的因素.使用仪器,我看copyImageBlockSetPNG,png_read_nowinflate占用了大部分的CPU(他们不分配时self.imageUIGraphicsGetImageFromCurrentImageContext())

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

有没有办法来解决这个问题?也许是为了让它只在第一次被绘制时解压缩PNG?

Jas*_*oco 23

你的问题是+imageWithContentsOfFile:缓存和延迟加载.如果你想做这样的事情,而是在你的后台队列中使用这个代码:

// Assuming ARC
NSData* imageFileData = [[NSData alloc] initWithContentsOfFile:thumbPath];
UIImage* savedImage = [[UIImage alloc] initWithData:imageFileData];

// Dispatch back to main queue and set image...
Run Code Online (Sandbox Code Playgroud)

现在,使用此代码,图像数据的实际解压缩仍然是懒惰的并且花费一点点,但不会像您在代码示例中使用延迟加载所获得的文件访问量那么多.

由于您仍然看到性能问题,您还可以强制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...
Run Code Online (Sandbox Code Playgroud)


Nic*_*ood 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);
        });
    });
}
Run Code Online (Sandbox Code Playgroud)

  • 对于上面的代码,我的实验表明,在我的情况下,UIGraphicsBeginImageContextWithOptions中的scale参数应该设置为1. (2认同)

Igo*_*gor 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);
}
Run Code Online (Sandbox Code Playgroud)

使用:

    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];
  });
}
Run Code Online (Sandbox Code Playgroud)