29

我有一个带有全屏 tableView 的应用程序,它显示一堆小图像。这些图像是从网络上提取的,在后台线程上处理,然后使用类似的东西保存到磁盘:

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
        });
    }
});

我的问题是,在第一种情况下,滚动很流畅。但是在第二种情况下(直接从磁盘读取图像),滚动是超级生涩的,即使加载了图像也是如此。绘图是导致滞后的原因。使用 Instruments,我看到了copyImageBlockSetPNGpng_read_now并且占用了大部分 cpu(分配给inflate时它们不是)self.imageUIGraphicsGetImageFromCurrentImageContext()

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

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

4

3 回答 3

24

您的问题是+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...
于 2012-04-13T22:41:30.777 回答
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);
        });
    });
}
于 2013-05-31T18:35:23.350 回答
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];
  });
}
于 2012-11-13T14:16:54.357 回答