Must drawInRect: for a separate context be executed on the main thread?
[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开发者_JAVA百科 and it crashed on me with "exit code 0" again just earlier. Back to the drawing board...
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.
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.
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.
精彩评论