CGImage/UIImage 在 UI 线程上延迟加载会导致卡顿
我的程序显示一个水平滚动表面,从左到右平铺有 UIImageViews。代码在 UI 线程上运行,以确保新可见的 UIImageView 分配有新加载的 UIImage。加载发生在后台线程上。
一切工作几乎都很好,除了每个图像变得可见时出现口吃。起初我以为我的后台工作人员锁定了 UI 线程中的某些内容。我花了很多时间查看它,最终意识到当 UIImage 第一次变得可见时,它正在 UI 线程上进行一些额外的延迟处理。这让我很困惑,因为我的工作线程有用于解压缩 JPEG 数据的显式代码。
不管怎样,凭直觉,我写了一些代码来渲染到后台线程上的临时图形上下文中——果然,卡顿消失了。 UIImage 现在已预加载到我的工作线程上。到目前为止,一切都很好。
问题是我的新“强制延迟加载图像”方法不可靠。它会导致间歇性的 EXC_BAD_ACCESS。我不知道 UIImage 在幕后实际上在做什么。也许它正在解压缩 JPEG 数据。无论如何,方法是:
+ (void)forceLazyLoadOfImage: (UIImage*)image
{
CGImageRef imgRef = image.CGImage;
CGFloat currentWidth = CGImageGetWidth(imgRef);
CGFloat currentHeight = CGImageGetHeight(imgRef);
CGRect bounds = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
CGAffineTransform transform = CGAffineTransformIdentity;
CGFloat scaleRatioX = bounds.size.width / currentWidth;
CGFloat scaleRatioY = bounds.size.height / currentHeight;
UIGraphicsBeginImageContext(bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, scaleRatioX, -scaleRatioY);
CGContextTranslateCTM(context, 0, -currentHeight);
CGContextConcatCTM(context, transform);
CGContextDrawImage(context, CGRectMake(0, 0, currentWidth, currentHeight), imgRef);
UIGraphicsEndImageContext();
}
EXC_BAD_ACCESS 发生在 CGContextDrawImage 行上。问题 1:我可以在 UI 线程以外的线程上执行此操作吗?问题 2:UIImage 实际上“预加载”是什么?问题3:解决这个问题的官方方法是什么?
感谢您阅读所有内容,任何建议将不胜感激!
My program displays a horizontal scrolling surface tiled with UIImageViews from left to right. Code runs on the UI thread to ensure that newly-visible UIImageViews have a freshly loaded UIImage assigned to them. The loading happens on a background thread.
Everything works almost fine, except there is a stutter as each image becomes visible. At first I thought my background worker was locking something in the UI thread. I spent a lot of time looking at it and eventually realized that the UIImage is doing some extra lazy processing on the UI thread when it first becomes visible. This puzzles me, since my worker thread has explicit code for decompressing JPEG data.
Anyway, on a hunch I wrote some code to render into a temporary graphics context on the background thread and - sure enough, the stutter went away. The UIImage is now being pre-loaded on my worker thread. So far so good.
The issue is that my new "force lazy load of image" method is unreliable. It causes intermittent EXC_BAD_ACCESS. I have no idea what UIImage is actually doing behind the scenes. Perhaps it is decompressing the JPEG data. Anyway, the method is:
+ (void)forceLazyLoadOfImage: (UIImage*)image
{
CGImageRef imgRef = image.CGImage;
CGFloat currentWidth = CGImageGetWidth(imgRef);
CGFloat currentHeight = CGImageGetHeight(imgRef);
CGRect bounds = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
CGAffineTransform transform = CGAffineTransformIdentity;
CGFloat scaleRatioX = bounds.size.width / currentWidth;
CGFloat scaleRatioY = bounds.size.height / currentHeight;
UIGraphicsBeginImageContext(bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, scaleRatioX, -scaleRatioY);
CGContextTranslateCTM(context, 0, -currentHeight);
CGContextConcatCTM(context, transform);
CGContextDrawImage(context, CGRectMake(0, 0, currentWidth, currentHeight), imgRef);
UIGraphicsEndImageContext();
}
And the EXC_BAD_ACCESS happens on the CGContextDrawImage line. QUESTION 1: Am I allowed to do this on a thread other than the UI thread? QUESTION 2: What is the UIImage actually "pre-loading"? QUESTION 3: What is the official way to solve this problem?
Thanks for reading all that, any advice would be greatly appreciated!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我也遇到了同样的口吃问题,在一些帮助下我在这里找到了正确的解决方案: iOS 中的非延迟图像加载
需要提及的两件重要事情:
-
我在这里发布了一个示例项目:SwapTest,它与苹果的照片应用程序加载/显示图像的性能大致相同。
I've had the same stuttering problem, with some help I figured out the proper solution here: Non-lazy image loading in iOS
Two important things to mention:
-
I've posted a sample project here: SwapTest, it has about the same performace as Apples' Photos app for loading/displaying images.
我使用 @jasamer 的 SwapTest UIImage 类别在工作线程(使用 NSOperationQueue)中强制加载我的大 UIImage(大约 3000x2100 px)。这可以减少将图像设置到 UIImageView 中时的卡顿时间到可接受的值(在 iPad1 上大约为 0.5 秒)。
这是 SwapTest UIImage 类别...再次感谢@jasamer :)
UIImage+ImmediateLoading.h 文件
UIImage+ImmediateLoading.m 文件
这就是我创建 NSOpeationQueue 并在主线程上设置图像的方式...
I used @jasamer's SwapTest UIImage category to force load my large UIImage (about 3000x2100 px) in a worker thread (with NSOperationQueue). This reduces the stutter time when setting the image into the UIImageView to an acceptable value (about 0.5 sec on iPad1).
Here is SwapTest UIImage category... thanks again @jasamer :)
UIImage+ImmediateLoading.h file
UIImage+ImmediateLoading.m file
And this is how I create NSOpeationQueue and set the image on main thread...
UIGraphics*
方法设计为仅从主线程调用。它们可能是你麻烦的根源。您可以将
UIGraphicsBeginImageContext()
替换为对CGBitmapContextCreate()
的调用;它涉及更多一些(您需要创建一个颜色空间,找出要创建的正确大小的缓冲区,然后自己分配它)。CG*
方法可以很好地从不同的线程运行。我不确定您如何初始化 UIImage,但如果您使用
imageNamed:
或initWithFile:
进行初始化,那么您可能可以强制它加载自己加载数据,然后调用initWithData:
。这种卡顿可能是由于惰性文件 I/O 造成的,因此使用数据对象初始化它不会为其提供从文件读取的选项。The
UIGraphics*
methods are designed to be called from the main thread only. They are probably the source of your trouble.You can replace
UIGraphicsBeginImageContext()
with a call toCGBitmapContextCreate()
; it's a little more involved (you need to create a color space, figure out the right sized buffer to create, and allocate it yourself). TheCG*
methods are fine to run from a different thread.I'm not sure how you're initializing UIImage, but if you're doing it with
imageNamed:
orinitWithFile:
then you might be able to force it to load by loading the data yourself and then callinginitWithData:
. The stutter is probably due to lazy file I/O, so initializing it with a data object won't give it the option of reading from a file.即使我使用数据初始化了图像,我也遇到了同样的问题。 (我猜数据也是延迟加载的?)我已经成功使用以下类别强制解码:
I had the same problem, even though I initialized the image using data. (I guess the data is loaded lazily, too?) I’ve succeeded to force decoding using the following category: