Objective c iOS中的非惰性图像加载

Objective c iOS中的非惰性图像加载,objective-c,ios,Objective C,Ios,我正在尝试将UIImage加载到背景线程中,然后在iPad上显示它们。但是,当我将imageViews的view属性设置为图像时,出现了一个停顿。 我很快发现iOS上的图像加载很慢,并找到了这个问题的部分解决方案: 这实际上会强制将图像加载到线程中,但在显示图像时仍然会出现口吃 您可以在这里找到我的示例项目:(编辑:),检查SwapTestViewController。尝试拖动图片以查看结巴 我创建的测试代码是这样的(forceLoad方法是从我上面发布的堆栈溢出问题中获取的): 我知道口吃可

我正在尝试将UIImage加载到背景线程中,然后在iPad上显示它们。但是,当我将imageViews的view属性设置为图像时,出现了一个停顿。 我很快发现iOS上的图像加载很慢,并找到了这个问题的部分解决方案:

这实际上会强制将图像加载到线程中,但在显示图像时仍然会出现口吃

您可以在这里找到我的示例项目:(编辑:),检查SwapTestViewController。尝试拖动图片以查看结巴

我创建的测试代码是这样的(forceLoad方法是从我上面发布的堆栈溢出问题中获取的):

我知道口吃可以避免的原因有两个:

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

(2) 在上述代码的修改版本中,占位符1和占位符2显示一次后,此代码不会导致口吃:

    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的技巧。我发现是CGSConvertBGRA8888或GBA8888花了很多时间,所以看起来是颜色转换造成了延迟。 下面是该方法的(反向)调用堆栈

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在后台线程上加载(非延迟解码)图形,将CGImageRef发布到主线程,并将其转换为UIImage。在您当前的实现中,它似乎可以工作(尽管您不希望出现口吃),但它不能保证工作正常。这一地区似乎有很多迷信和不良行为,所以你设法找到一些不好的建议也就不足为奇了

建议在后台线程上运行:

// 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的相关参数传递,并让CoreGraphics整理自己的存储

这与您发布到的解决方案不同,因为它:

  • 从PNG数据源创建CGImage-延迟加载适用,因此这不一定是完全加载和解压缩的图像
  • 创建与PNG大小相同的位图上下文
  • 将PNG数据源中的CGImage绘制到位图上下文中-这将强制完全加载和解压缩,因为实际颜色值必须放在我们可以从C数组访问它们的地方。此步骤仅限于链接到的力载荷
  • 将位图上下文转换为图像
  • 将该图像发布到主线程,可能会成为UIImage

  • 因此,加载的对象和显示的对象之间没有连续性;像素数据通过一个C数组(因此,没有机会进行隐藏的恶作剧),只有将其正确地放入数组中,才有可能生成最终图像。

    好的,在Tommy的大量帮助下解决了这个问题。谢谢大家!

    如果您使用

            CGContextRef imageContext =
            CGBitmapContextCreate(imageBuffer, width, height, 8, width*4, colourSpace,
                                  kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
    
    主运行循环将不再导致主线程上的任何转换。 显示现在是黄油般光滑。(这些标志有点违反直觉,有人知道为什么必须先选择KCGimageAlpha预乘吗?)

    编辑:

    已上载固定示例项目:。 如果您在图像性能方面有问题,这是一个很好的起点

    怎么样


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

    感谢您提供的信息;UIImage非常方便;-)我用您的代码替换了我的方法,但结巴仍然存在。尝试通过imageView.layer.contents=thegimage;设置图像;,看起来它稍微改善了一些东西,但还不够好。有没有可能是由于将图像数据复制到图形内存所需的时间造成的,或者是因为它必须首先更改图像表示的某些内容?这肯定不是图像加载/解码的成本-任何加载/解码都不可能与上述代码有关。您是否尝试添加一些NSTimeInterval time=[NSDate timeIntervalSinceReferenceDate]并记录差异?这将告诉你事情实际需要多长时间,这样你就可以找出真正的罪魁祸首。顺便说一下,UIImage+imageWithContentsOfFile:在iOS4之后是线程安全的。您可以在后台线程中使用此方法@Kazuki:这是有道理的;UIImage对用户交互一无所知,所以您可能认为它是UIKit中最容易去除线程限制的部分之一@朱利安:答案已编辑。很高兴知道这些方法现在是线程安全的。有了这样一个UIImage类别,代码看起来就更好了,我将更新我的示例项目。不过,它需要经过调整的位掩码选项来消除口吃。我从使用代码加载图像中获得了足够的好处,实际上我只是加载图像并放弃它们,然后像往常一样继续我的工作。我将方法略微更改为
    imageImmediateLoadWithName
    ,因此
    [UIImage-imageimediateloadwithname:@“myImage.png”]这足以使后续对映像的引用更快。是否有“除了UIImage-initWithContentsOfFile:,-imageWithCGImage:和CG*在iOS4之后是线程安全的”的引用?UIImage文档或iOS更改日志没有任何影响(
    
            CGContextRef imageContext =
            CGBitmapContextCreate(imageBuffer, width, height, 8, width*4, colourSpace,
                                  kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);