必须在主线程上执行单独的上下文的 drawInRect: 吗?

发布于 2024-09-08 16:56:41 字数 3611 浏览 6 评论 0原文

[更新:此问题已解决;问题不在 drawInRect: 中,而是在 UIGraphicsBeginImageContext() 中]

在我的应用程序中,我抓取了一堆大图像,将它们裁剪为缩略图大小并存储用于预览的缩略图。

请注意,我是在单独的图像上下文中执行此操作 - 这不是重绘屏幕上的 UIView。

这段代码相当密集,因此我在单独的线程中运行它。实际的缩放看起来像这样,并且是 UIImage 之上的类别实现:

- (UIImage *) scaledImageWithWidth:(CGFloat)width andHeight:(CGFloat)height
{
    CGRect rect = CGRectMake(0.0, 0.0, width, height);
    UIGraphicsBeginImageContext(rect.size);
    [self drawInRect:rect]; // <-- crashing on this line
    UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return scaledImage;
}

这是从一个单独的方法调用的,该方法依次循环遍历图像并进行处理。对上述方法的实际调用如下所示:

UIImage *small = [bigger scaledImageWithWidth:24.f andHeight:32.f];

这在大多数情况下都有效,但偶尔我会收到 EXC_BAD_ACCESS

Backtrace:

#0  0x330d678c in ripc_RenderImage ()
#1  0x330dd5aa in ripc_DrawImage ()
#2  0x300e3276 in CGContextDelegateDrawImage ()
#3  0x300e321a in CGContextDrawImage ()
#4  0x315164c8 in -[UIImage drawInRect:blendMode:alpha:] ()
#5  0x31516098 in -[UIImage drawInRect:] ()
#6  0x0000d6e4 in -[UIImage(Scaling) scaledImageWithWidth:andHeight:] (self=0x169320, _cmd=0x30e6e, width=48, height=64) at /Users/me/Documents/svn/app/trunk/Classes/UIImage+Scaling.m:20
#7  0x00027df0 in -[mgMinimap loadThumbnails] (self=0x13df00, _cmd=0x30d05) at /Users/me/Documents/svn/app/trunk/Classes/mgMinimap.m:167
#8  0x32b15bd0 in -[NSThread main] ()
#9  0x32b81cfe in __NSThread__main__ ()
#10 0x30c8f78c in _pthread_start ()
#11 0x30c85078 in thread_start ()

[更新 4] 当我在模拟器中运行此命令并且发生此问题时,控制台还会显示以下内容:

// the below is before loading the first thumbnail
<Error>: CGContextSaveGState: invalid context
<Error>: CGContextSetBlendMode: invalid context
<Error>: CGContextSetAlpha: invalid context
<Error>: CGContextTranslateCTM: invalid context
<Error>: CGContextScaleCTM: invalid context
<Error>: CGContextDrawImage: invalid context
<Error>: CGContextRestoreGState: invalid context
<Error>: CGBitmapContextCreateImage: invalid context
// here, the first thumbnail has finished loading and the second one
// is about to be generated
<Error>: CGContextSetStrokeColorWithColor: invalid context
<Error>: CGContextSetFillColorWithColor: invalid context

我的直觉是,当操作系统运行时,我偶尔会尝试 drawInRect:还尝试画一些东西,这偶尔会导致崩溃。我一直认为只要不在实际屏幕上绘图,这是可以接受的——难道不是这样吗?或者如果是这种情况,知道可能是什么原因造成的吗?

更新(r2):我忘了提到这个应用程序正在相当严格的内存限制下运行(我在任何给定时间加载了很多图像,并且这些图像被换入/换出),所以这可能是运行的情况内存不足(继续阅读——事实并非如此)。不过,我不确定如何验证这一点,所以对此的想法也将受到欢迎。我确实通过严格减少加载的图像数量并添加检查以确保它们被正确释放(确实如此,并且崩溃仍然发生)来验证这一点。

更新3:我以为我找到了问题。下面是我在崩溃再次发生之前写的答案:

代码(在我发布此问题之后)偶尔会以退出代码 0 开始退出,有时会以退出代码 10 (SIGBUS) 退出0 表示“没有错误”,所以这非常奇怪。 10 似乎意味着一切,所以这也没有帮助。不过,当崩溃发生时,drawInRect: 调用是一个很大的提示。

循环获取缩略图会生成大量自动发布的图像。我有一个自动释放池,但它包装了整个 for 循环。我在 for 循环中添加了第二个自动释放池:

- (void)loadThumbnails
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    for (...) {
        NSAutoreleasePool *cyclePool = 
           [[NSAutoreleasePool alloc] init]; // <-- here
        UIImage *bigger = ...;
        UIImage *small = [bigger scaledImageWithWidth:24.f andHeight:32.f];
        UIImage *bloated = [i scaledImageWithWidth:48.f andHeight:64.f];
        [cyclePool release]; // <-- ending here
    }
    [pool release];
}

我认为上面的内容解决了问题,直到我运行应用程序,它在早些时候再次崩溃并显示“退出代码 0”。回到绘图板...

[update: this problem has been resolved; the issue was not in drawInRect: but in UIGraphicsBeginImageContext()]

In my app, I'm grabbing a bunch of large images, cropping them down to thumbnail size and storing the thumbnails for previewing.

Note that I'm doing this in a separate image context -- this is not about redrawing a UIView that is on the screen.

This code is rather intensive so I'm running it in a separate thread. The actual scaling looks like this, and is a category implementation on top of UIImage:

- (UIImage *) scaledImageWithWidth:(CGFloat)width andHeight:(CGFloat)height
{
    CGRect rect = CGRectMake(0.0, 0.0, width, height);
    UIGraphicsBeginImageContext(rect.size);
    [self drawInRect:rect]; // <-- crashing on this line
    UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return scaledImage;
}

This is called from a separate method, which loops through the images in turn and does the processing. The actual call to the above method looks like this:

UIImage *small = [bigger scaledImageWithWidth:24.f andHeight:32.f];

This all works most of the time, but occasionally I get an EXC_BAD_ACCESS.

Backtrace:

#0  0x330d678c in ripc_RenderImage ()
#1  0x330dd5aa in ripc_DrawImage ()
#2  0x300e3276 in CGContextDelegateDrawImage ()
#3  0x300e321a in CGContextDrawImage ()
#4  0x315164c8 in -[UIImage drawInRect:blendMode:alpha:] ()
#5  0x31516098 in -[UIImage drawInRect:] ()
#6  0x0000d6e4 in -[UIImage(Scaling) scaledImageWithWidth:andHeight:] (self=0x169320, _cmd=0x30e6e, width=48, height=64) at /Users/me/Documents/svn/app/trunk/Classes/UIImage+Scaling.m:20
#7  0x00027df0 in -[mgMinimap loadThumbnails] (self=0x13df00, _cmd=0x30d05) at /Users/me/Documents/svn/app/trunk/Classes/mgMinimap.m:167
#8  0x32b15bd0 in -[NSThread main] ()
#9  0x32b81cfe in __NSThread__main__ ()
#10 0x30c8f78c in _pthread_start ()
#11 0x30c85078 in thread_start ()

[update 4] When I run this in the Simulator, and this problem happens, the console additionally shows the following:

// the below is before loading the first thumbnail
<Error>: CGContextSaveGState: invalid context
<Error>: CGContextSetBlendMode: invalid context
<Error>: CGContextSetAlpha: invalid context
<Error>: CGContextTranslateCTM: invalid context
<Error>: CGContextScaleCTM: invalid context
<Error>: CGContextDrawImage: invalid context
<Error>: CGContextRestoreGState: invalid context
<Error>: CGBitmapContextCreateImage: invalid context
// here, the first thumbnail has finished loading and the second one
// is about to be generated
<Error>: CGContextSetStrokeColorWithColor: invalid context
<Error>: CGContextSetFillColorWithColor: invalid context

My gut feeling is that I occasionally end up trying to drawInRect: while the OS is also trying to draw something, which results, occasionally, in a crash. I always presumed that as long as you don't draw on the actual screen, this is acceptable -- is this not the case? Or if it is the case, any idea what might be causing this?

Update (r2): I forgot to mention that this app is running under rather severe memory constraints (I've got a lot of images loaded at any given time and these are swapped in/out), so this may be a case of running out of memory (read on -- it's not). I'm not sure how to verify that, though, so thoughts on this would be welcome too. I did verify this by severely cutting down on the number of images being loaded and adding a check to make sure they're properly deallocated (they are, and the crash still occurs).

Update 3: I thought I found the problem. Below is the answer I wrote, before the crash happened again:

The code would (after I posted this question) occasionally start exiting with exit code 0, and sometimes with exit code 10 (SIGBUS). 0 means "no error", so that was extremely odd. 10 seems to mean a bit of everything so that was unhelpful too. The drawInRect: call was a big hint, though, when the crash happened there.

The looping through to get the thumbnails was generating a lot of autoreleased images. I had an autorelease pool but it was wrapping the entire for loop. I added a second autorelease pool within the for loop:

- (void)loadThumbnails
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    for (...) {
        NSAutoreleasePool *cyclePool = 
           [[NSAutoreleasePool alloc] init]; // <-- here
        UIImage *bigger = ...;
        UIImage *small = [bigger scaledImageWithWidth:24.f andHeight:32.f];
        UIImage *bloated = [i scaledImageWithWidth:48.f andHeight:64.f];
        [cyclePool release]; // <-- ending here
    }
    [pool release];
}

I thought the above fixed the issue, until I ran the app and it crashed on me with "exit code 0" again just earlier. Back to the drawing board...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

┊风居住的梦幻卍 2024-09-15 16:56:41

您是否看过 Matt Gemmell 的最新版本 MGImageUtilities ?我从他在 github 上的来源中提取了这一点:

// Create appropriately modified image.
UIImage *image;
UIGraphicsBeginImageContextWithOptions(destRect.size, NO, 0.0); // 0.0 for scale means "correct scale for device's main screen".
CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], sourceRect); // cropping happens here.
image = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:self.imageOrientation]; // create cropped UIImage.
[image drawInRect:destRect]; // the actual scaling happens here, and orientation is taken care of automatically.
CGImageRelease(sourceImg);
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

不确定线程​​安全是否是问题所在,但在沿着这条路走得太远之前,可能值得尝试一下 Matt 的代码。

Have you looked at Matt Gemmell's latest release, MGImageUtilities? I extracted this from his source on github:

// Create appropriately modified image.
UIImage *image;
UIGraphicsBeginImageContextWithOptions(destRect.size, NO, 0.0); // 0.0 for scale means "correct scale for device's main screen".
CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], sourceRect); // cropping happens here.
image = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:self.imageOrientation]; // create cropped UIImage.
[image drawInRect:destRect]; // the actual scaling happens here, and orientation is taken care of automatically.
CGImageRelease(sourceImg);
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

Not sure if thread safety is the issue, but it may be worth trying Matt's code before going too far down that path.

惯饮孤独 2024-09-15 16:56:41

事实证明,有两个答案:

“必须在主线程上执行单独的上下文的 drawInRect: 吗?”答案是否定的,事实并非如此。

但是,UIGraphicsBeginImageContext 必须。这实际上就是发生崩溃的原因。直到(无效的)图形上下文被更改时,崩溃才显现出来,这就是崩溃发生在 drawInRect: 行上的原因。

解决方案是停止使用 UIGraphicsContext 并使用 CGBitmapContext,它是线程安全的。

It turns out, there are two answers:

"Must drawInRect: for a separate context be executed on the main thread?" The answer is no, it doesn't.

However, UIGraphicsBeginImageContext must. This in fact is the reason for the crashing that occured. The crash didn't reveal itself until the (invalid) graphics context was being altered, which is why the crash occured on the drawInRect: line.

The solution was to stop using UIGraphicsContext and instead use CGBitmapContext, which is thread safe.

浅唱々樱花落 2024-09-15 16:56:41

我有一种感觉,您不应该直接调用 drawInRect ,因为它可能不是线程安全的。您应该在视图上调用 setNeedsDisplay ,以便在安全时向 drawInRect 发送消息。

I have a feeling you shouldn't be calling drawInRect directly as it may not be thread safe. You should be calling setNeedsDisplay on the view which will send a message to drawInRect when it is safe to do so.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文