gpt4 book ai didi

objective-c - iOS中的非延迟图像加载

转载 作者:IT老高 更新时间:2023-10-28 11:45:08 30 4
gpt4 key购买 nike

我正在尝试在后台线程中加载 UIImages,然后在 iPad 上显示它们。但是,当我将 imageViews 的 View 属性设置为图像时,会出现口吃。我很快就发现iOS上的图片加载很懒,并在这个问题中找到了部分解决方案:

CGImage/UIImage lazily loading on UI thread causes stutter

这实际上是强制在线程中加载图像,但是显示图像时仍然卡顿。

您可以在这里找到我的示例项目:http://www.jasamer.com/files/SwapTest.zip (编辑:fixed version),检查 SwapTestViewController。尝试拖动图片查看卡顿。

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

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) Apple 能够在照片应用中加载图像而不会卡顿

(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 在后台线程上加载(和非延迟解码)图形,将 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 自行整理存储即可。

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

  1. 从 PNG 数据源创建 CGImage - 延迟加载应用,因此这不一定是完全加载和解压缩的图像
  2. 创建与 PNG 大小相同的位图上下文
  3. 将来自 PNG 数据源的 CGImage 绘制到位图上下文中——这应该强制完全加载和解压缩,因为实际的颜色值必须放在我们可以从 C 数组中访问它们的地方。此步骤与您链接到的 forceLoad 一样。
  4. 将位图上下文转换为图像
  5. 将该图像发布到主线程,大概会成为 UIImage

所以加载的东西和显示的东西之间没有对象的连续性;像素数据通过一个 C 数组(因此,没有机会隐藏恶作剧),只有正确放入数组才能制作最终图像。

关于objective-c - iOS中的非延迟图像加载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5266272/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com