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

时间:2010-07-08 20:08:55

标签: iphone objective-c multithreading uiimage

[更新:此问题已解决;问题不在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”崩溃了。回到绘图板......

3 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:1)

事实证明,有两个答案:

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

但是,UIGraphicsBeginImageContext必须。这实际上是发生崩溃的原因。在(无效)图形上下文被更改之前,崩溃没有显示出来,这就是drawInRect:行发生崩溃的原因。

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

答案 2 :(得分:0)

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