iOS中的非懒惰图片加载

我试图在后台线程中加载UIImage,然后在iPad上显示它们。 但是,当我将imageViews的视图属性设置为图像时出现了一个口吃。 我很快就知道iOS上的图片加载是懒惰的,并且在这个问题中find了部分解决scheme:

在UI线程上延迟加载CGImage / UIImage导致口吃

这实际上强制图像被加载到线程中,但是在显示图像时仍然存在一个口吃。

你可以在这里find我的示例项目: http : //www.jasamer.com/files/SwapTest.zip (编辑: 固定版本 ),检查SwapTestViewController。 尝试拖动图片看到口吃。

我创build的testing代码是这样的(forceLoad方法是从上面发布的堆栈溢出问题中提取的):

NSArray* imagePaths = [NSArray arrayWithObjects: [[NSBundle mainBundle] pathForResource: @"a.png" ofType: nil], [[NSBundle mainBundle] pathForResource: @"b.png" ofType: nil], nil]; NSOperationQueue* queue = [[NSOperationQueue alloc] init]; [queue addOperationWithBlock: ^(void) { int imageIndex = 0; while (true) { UIImage* image = [[UIImage alloc] initWithContentsOfFile: [imagePaths objectAtIndex: imageIndex]]; imageIndex = (imageIndex+1)%2; [image forceLoad]; //What's missing here? [self performSelectorOnMainThread: @selector(setImage:) withObject: image waitUntilDone: YES]; [image release]; } }]; 

为什么我知道口吃可以避免有两个原因:

(1)苹果能够加载图片,而不会在照片应用程序中出现口吃

(2)在以上代码的修改版本中,placeholder1和placeholder2已经显示一次后,此代码不会导致口吃:

  UIImage* placeholder1 = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource: @"a.png" ofType: nil]]; [placeholder1 forceLoad]; UIImage* placeholder2 = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource: @"b.png" ofType: nil]]; [placeholder2 forceLoad]; NSArray* imagePaths = [NSArray arrayWithObjects: [[NSBundle mainBundle] pathForResource: @"a.png" ofType: nil], [[NSBundle mainBundle] pathForResource: @"b.png" ofType: nil], nil]; NSOperationQueue* queue = [[NSOperationQueue alloc] init]; [queue addOperationWithBlock: ^(void) { int imageIndex = 0; while (true) { //The image is not actually used here - just to prove that the background thread isn't causing the stutter UIImage* image = [[UIImage alloc] initWithContentsOfFile: [imagePaths objectAtIndex: imageIndex]]; imageIndex = (imageIndex+1)%2; [image forceLoad]; if (self.imageView.image==placeholder1) { [self performSelectorOnMainThread: @selector(setImage:) withObject: placeholder2 waitUntilDone: YES]; } else { [self performSelectorOnMainThread: @selector(setImage:) withObject: placeholder1 waitUntilDone: YES]; } [image release]; } }]; 

但是,我不能把所有的图像都留在记忆里。

这意味着forceLoad不会完成全部工作 – 在图像实际显示之前还有其他事情正在进行。 有谁知道这是什么,我怎么可以把它放到后台线程?

谢谢,朱利安

更新

使用了一些Tommys提示。 我想到的是CGSConvertBGRA8888toRGBA8888花了这么多时间,所以看起来这是一个颜色转换造成的滞后。 这是该方法的(反转)调用堆栈。

 Running Symbol Name 6609.0ms CGSConvertBGRA8888toRGBA8888 6609.0ms ripl_Mark 6609.0ms ripl_BltImage 6609.0ms RIPLayerBltImage 6609.0ms ripc_RenderImage 6609.0ms ripc_DrawImage 6609.0ms CGContextDelegateDrawImage 6609.0ms CGContextDrawImage 6609.0ms CA::Render::create_image_by_rendering(CGImage*, CGColorSpace*, bool) 6609.0ms CA::Render::create_image(CGImage*, CGColorSpace*, bool) 6609.0ms CA::Render::copy_image(CGImage*, CGColorSpace*, bool) 6609.0ms CA::Render::prepare_image(CGImage*, CGColorSpace*, bool) 6609.0ms CALayerPrepareCommit_(CALayer*, CA::Transaction*) 6609.0ms CALayerPrepareCommit_(CALayer*, CA::Transaction*) 6609.0ms CALayerPrepareCommit_(CALayer*, CA::Transaction*) 6609.0ms CALayerPrepareCommit_(CALayer*, CA::Transaction*) 6609.0ms CALayerPrepareCommit 6609.0ms CA::Context::commit_transaction(CA::Transaction*) 6609.0ms CA::Transaction::commit() 6609.0ms CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) 6609.0ms __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ 6609.0ms __CFRunLoopDoObservers 6609.0ms __CFRunLoopRun 6609.0ms CFRunLoopRunSpecific 6609.0ms CFRunLoopRunInMode 6609.0ms GSEventRunModal 6609.0ms GSEventRun 6609.0ms -[UIApplication _run] 6609.0ms UIApplicationMain 6609.0ms main 

可悲的是,他提出的最后一个位掩码变化没有改变任何东西。

UIKit只能在主线程上使用。 因此,您的代码在技术上是无效的,因为您从主线程以外的线程使用UIImage。 您应该单独使用CoreGraphics在后台线程上加载(和非延迟解码)graphics,将CGImageRef发布到主线程,并将其转换为UIImage。 在你当前的实现中,它似乎可以工作(虽然你不想要这样的结局),但是不能保证。 在这方面似乎有很多迷信和坏习惯,所以你find一些不好的build议也就不足为奇了。

build议在后台线程上运行:

 // get a data provider referencing the relevant file CGDataProviderRef dataProvider = CGDataProviderCreateWithFilename(filename); // use the data provider to get a CGImage; release the data provider CGImageRef image = CGImageCreateWithPNGDataProvider(dataProvider, NULL, NO, kCGRenderingIntentDefault); CGDataProviderRelease(dataProvider); // make a bitmap context of a suitable size to draw to, forcing decode size_t width = CGImageGetWidth(image); size_t height = CGImageGetHeight(image); unsigned char *imageBuffer = (unsigned char *)malloc(width*height*4); CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef imageContext = CGBitmapContextCreate(imageBuffer, width, height, 8, width*4, colourSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little); CGColorSpaceRelease(colourSpace); // draw the image to the context, release it CGContextDrawImage(imageContext, CGRectMake(0, 0, width, height), image); CGImageRelease(image); // now get an image ref from the context CGImageRef outputImage = CGBitmapContextCreateImage(imageContext); // post that off to the main thread, where you might do something like // [UIImage imageWithCGImage:outputImage] [self performSelectorOnMainThread:@selector(haveThisImage:) withObject:[NSValue valueWithPointer:outputImage] waitUntilDone:YES]; // clean up CGImageRelease(outputImage); CGContextRelease(imageContext); free(imageBuffer); 

如果你使用的是iOS 4或更高版本,则不需要执行malloc / free,只需将NULL作为CGBitmapContextCreate的相关parameter passing给CoreGraphics即可。

这与您发布的解决scheme不同,因为它:

  1. 从PNG数据源创build一个CGImage – 延迟加载应用,所以这不一定是一个完全加载和解压缩的图像
  2. 创build一个与PNG大小相同的位图上下文
  3. 从PNG数据源中将CGImage绘制到位图上下文中 – 这应该强制完全加载和解压缩,因为实际的颜色值必须放在我们可以从C数组访问它们的地方。 这一步就像你所链接的forceLoad一样。
  4. 将位图上下文转换为图像
  5. 这个post可以映射到主线程,据推测可以成为UIImage

所以装载的东西和显示的东西之间没有物体的连续性, 像素数据通过一个C数组(所以,没有机会隐藏的诡计),只有当它被正确放入arrays才有可能做出最终的图像。

好吧,想出来 – 汤米给了我很多帮助。 谢谢!

如果你创build你的上下文

  CGContextRef imageContext = CGBitmapContextCreate(imageBuffer, width, height, 8, width*4, colourSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little); 

主运行循环不会再对主线程进行任何转换。 显示现在是光滑的。 (这些标志有点不直观,有谁知道为什么你必须selectkCGImageAlphaPremultipliedFirst?)

编辑:

上传了固定样本项目: http : //www.jasamer.com/files/SwapTest-Fixed.zip 。 如果你在图像performance方面有问题,这是一个很好的起点!

如何UIImage + ImmediateLoad.m ?

它立即解码图像。 除了UIImage -initWithContentsOfFile :, -imageWithCGImage:和CG *在iOS4之后是线程安全的。