【问题标题】:Must drawInRect: for a separate context be executed on the main thread?必须drawInRect:在主线程上执行单独的上下文吗?
【发布时间】:2026-01-29 17:30:01
【问题描述】:

[更新:这个问题已经解决;问题不在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

回溯:

#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”。回到绘图板...

【问题讨论】:

    标签: iphone objective-c multithreading uiimage


    【解决方案1】:

    您看过 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 的代码。

    【讨论】:

    • 有趣。将要看。感谢您的提示!
    • 我仔细研究了这一点,甚至尝试以同样的方式进行操作(不过我已经有点过了),但没有运气。同样的调用 (drawInRect:),同样的错误。
    【解决方案2】:

    原来,有两个答案:

    “必须drawInRect: 在主线程上执行单独的上下文吗?”答案是否定的,它没有。

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

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

    【讨论】:

    • 从 iOS4.0 开始,在 UIKit 中绘制到图形上下文现在是线程安全的。具体来说:用于访问和操作图形上下文的例程现在可以正确处理驻留在不同线程上的上下文。参考:developer.apple.com/library/ios/#releasenotes/General/…
    • 感谢您的发现,MattyG。绝对有趣。
    • @Kalle :那你改了什么代码?可以分享一下吗?
    • @FahimParkar 这个答案是 4 岁。我不记得了,所以最好只问一个你自己的新问题。
    【解决方案3】:

    我觉得你不应该直接调用drawInRect,因为它可能不是线程安全的。您应该在视图上调用 setNeedsDisplay,这将在安全的情况下向 drawInRect 发送消息。

    【讨论】:

    • 如果您查看代码,您会注意到这是不可能的,因为我在单独的图像上下文中绘制矩形。
    • 通过观看由 Apple 员工提供的斯坦福 iTunes U 教程,他们明确表示您永远不会直接调用 drawRect。我不知道这是否同样适用于 drawInRect。
    • 它没有。大约有 5 个 Apple 示例可以执行此调用。一个例子在这里(来自食谱示例代码):developer.apple.com/iphone/library/samplecode/…