Quartz PDF-context based function fails (occasionally) with BAD_ACCESS - iphone

Im Getting a Frequent enough BAD_ACCESS when i call this Quartz function:
CGContextDrawPDFPage ((CGContextRef)context, (CGPDFPageRef)pageRef);
Here is how i call it:
CGContextRef context = UIGraphicsGetCurrentContext();
//translate, scale
CGPDFPageRef myPageRef = CGPDFDocumentGetPage ([PDFDocument sharedPDFDocument].documentData, pageNumber);
CGContextDrawPDFPage (context, myPageRef);//BAD_ACCESS HERE
return UIGraphicsGetImageFromCurrentImageContext();//autoreleased- saved on return
//close context
Neither variable is Nil, or have been released/auto released.
Here is the Stack trace, from the debugger:
Can anyone shed any light on this? Even some pointers on how i might investigate this better. it might not even be function specific.
I probobly should mention this function is performed on a separate thread.

Check the documentation for that returns your myPageRef and see if you have to retain it.

At the time of writing the question, UIGraphicsGetImageFromCurrentImageContext and such functions where not thread safe, this was my issue. As of iOs 4.1+ many UI function are now thread safe.

Related

Core Text Memory allocation issue

I inspected my app with the allocation instrument and I discovered that this code down here cause me an allocation issue. The method returns the suggested height of a squared area filled with the passed attributed string; I need this in order to calculate how much space I need to draw that text and then generating book pages:
- (CGFloat)boundingHeightForWidth:(CGFloat)inWidth ForAttributedString:(NSAttributedString *)attributedString
{
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFMutableAttributedStringRef)attributedString);
CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), NULL, CGSizeMake(inWidth, 10000), NULL);
CFRelease(framesetter);
return suggestedSize.height ;
}
Since I am calling this method many and many times during the flow, I am wondering how this is causing up to 7MB of memory allocation.. I thought releasing the frame setter could be enough, am I wrong?
I did some detailed debugging WRT to this issue, you can find the results in my answer to this question. There are a couple of things you could try. 1, does you app do these allocations in a secondary thread, if yes then does moving them to the main thread make the lost memory go away? Two, you could hold on to the CTFramesetterRef and then invoke CTFramesetterSuggestFrameSizeWithConstraints over and over with the same framesetter. The leak appears to be in the CTFramesetterCreateWithAttributedString() call, so perhaps you could minimize the leak by not invoking that method so many times.

How to access CGContext for creating CGLayer offscreen

Intention: creating CGLayer offscreen, draw some complicated stuff on it from second thread and use it later for quick drawing on main thread
Problem: CGLayerCreateWithContext(context, size, info) expects an already existing CGContext so that it knows for what kind of context it needs to be optimized.
Solution I found so far: CGContextRef ctx = UIGraphicSetCurrentContext()
but this function doesn't seem to exist anymore.
Question: Isn't there another way to access something like a default context? Or do i really need to wait for the first drawRect: call just for accessing UIGraphicsGetCurrentContext() and creating all CGLayers from main thread with one wasted drawing run?
you can create an image context by doing something like:
UIGraphicsBeginImageContext(rect);
// your drawing code
UIGraphicsEndImageContext();
with that said, i'm not sure you can do this from a thread other than main. worth a try, though.

Using GCD/asynchronous code in drawRect:

I'm trying to load an image from a url inside of drawRect:. Our image-loading code is a method that looks like this: - (void) loadImage:(NSURL*)url done:(void(^)(UIImage*))done;, which creates an asynchronous NSURLConnection and calls back with the image.
So, my code in drawRect: looks like this:
CGContextRef context = UIGraphicsGetCurrentContext()
[service loadImage:url done:^(UIImage * image){
CGContextDrawImage(context, frame, image.CGImage);
}];
Unfortunately, this doesn't work. The image is never drawn.
I've also tried using synchronous connections ([NSData dataWithContentsOfUrl:]), but it blocks the thread and slows things down unnecessarily. I don't want to use a UIImageView.
What is the correct way to do this? Thanks!
I think the correct way would either be to load the image beforehand or draw the image as soon as you receive it by either calling "setNeedsDisplayInRect:" or doing your own drawing handling in a separate method (iOS might discourage you from doing this though, I am not sure). Obviously the setNeedsDisplayInRect variation would require you to have the image at hand for drawing the second time around (cache it).
If you load the image asynchronously your drawRect method will moste likely have exited before the image is loaded and the draw focus is no longer locked on your drawRect, thus making it impossible to draw the image in the context. Did you try to set a breakpoint to verify this though? I might be wrong about the locking, but thought I read something alike this at some point.
Think it through--why would it run? There's no guarantee that context is still the current context (or even that its backing store is still fully valid) by the time the block is entered. By using an asynchronous callback, be it a block or a delegate method or something else, you're intentionally deferring code to a later point in time. The call to -drawRect: has ended and is off the stack long before your block is entered.
The first thing I would try is to make sure the async handler runs on the main thread:
[service loadImage:url done:^(UIImage * image) {
dispatch_async(dispatch_get_main_queue(), ^{
CGContextDrawImage(context, frame, image.CGImage);
});
}];
If that does not help, I would start to wonder if the context is still valid. The pointer is, but the context probably not. Maybe you could move the context acquisition to the handler:
[service loadImage:url done:^(UIImage * image) {
dispatch_async(dispatch_get_main_queue(), ^{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, frame, image.CGImage);
…
});
}];
Not sure if that makes sense API-wise, I don’t do much stuff like this.
Have you tried:
__block CGContextRef context = UIGraphicsGetCurrentContext()
[service loadImage:url done:^(UIImage * image){
CGContextDrawImage(context, frame, image.CGImage);
}];
You may have to do the same thing with frame, so when the block returns it knows what context and frame it should modify.

Unable to release a CGContextRef (context is from a CGLayer)

When you create a CGLayer like so, and then get the context...it appears to be impossible to release the CGContextRef?
Releasing the CGLayerRef itself (apparently) works fine.
You'd think you could release the CGContextRef just before releasing the CGLayer - but no? Nor can you release the CGContextRef just after releasing the CGLayer.
If you release the CGContextRef, the app crashes.
CGLayerRef aether = CGLayerCreateWithContext(
UIGraphicsGetCurrentContext(), CGSizeMake(1024,768), NULL);
CGContextRef weird = CGLayerGetContext(aether);
// paths, strokes, filling etc
// paths, strokes, filling etc
// try releasing weird here
CGLayerRelease(aether);
// or, try releasing weird here
Does anyone know what is going on here? (Note further that CGContextRelease is indeed just the same as CFRelease, with some nil checking.)
In fact should you never manually release CGContextRef? Does anyone know? Cheers.
CGContextRelease(weird); // impossible, not necessary, doesn't work???
Regarding Joel's spectacular answer below:
Is releasing the CGLayerRef correct and proper? Joel has pointed out:
"Yes, since the function you're obtaining it from has 'Create' in its signature. See: documentation/CoreFoundation/"
You do not own the context returned from CGLayerGetContext, and so should not release it*. In particular, see http://developer.apple.com/library/mac/#documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/Ownership.html#//apple_ref/doc/writerid/cfGetRule for information regarding 'Get' functions in Core Foundation.
*: at least, you shouldn't release it given your example code. If you retained it first (CGContextRetain(weird)), then you should have a CGContextRelease to balance it.

NSZombieEnabled breaking working code?

I have the following method in UIImageManipulation.m:
+(UIImage *)scaleImage:(UIImage *)source toSize:(CGSize)size
{
UIImage *scaledImage = nil;
if (source != nil)
{
UIGraphicsBeginImageContext(size);
[source drawInRect:CGRectMake(0, 0, size.width, size.height)];
scaledImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
return scaledImage;
}
I am calling it in a different view with:
imageFromFile = [UIImageManipulator scaleImage:imageFromFile toSize:imageView.frame.size];
(imageView is a UIImageView allocated earlier)
This is working great in my code. I resizes the image perfectly, and throws zero errors. I also don't have anything pop up under build -> analyze. But the second I turn on NSZombieEnabled to debug a different EXC_BAD_ACCESS issue, the code breaks. Every single time. I can turn NSZombieEnabled off, code runs great. I turn it on, and boom. Broken. I comment out the call, and it works again. Every single time, it gives me an error in the console: -[UIImage release]: message sent to deallocated instance 0x3b1d600. This error doesn't appear if `NSZombieEnabled is turned off.
Any ideas?
--EDIT--
Ok, This is killing me. I have stuck breakpoints everywhere I can, and I still cannot get a hold of this thing. Here is the full code when I call the scaleImage method:
-(void)setupImageButton
{
UIImage *imageFromFile;
if (object.imageAttribute == nil) {
imageFromFile = [UIImage imageNamed:#"no-image.png"];
} else {
imageFromFile = object.imageAttribute;
}
UIImage *scaledImage = [UIImageManipulator scaleImage:imageFromFile toSize:imageButton.frame.size];
UIImage *roundedImage = [UIImageManipulator makeRoundCornerImage:scaledImage :10 :10 withBorder:YES];
[imageButton setBackgroundImage:roundedImage forState:UIControlStateNormal];
}
The other UIImageManipulator method (makeRoundCornerImage) shouldn't be causing the error, but just in case I'm overlooking something, I threw the entire file up on github here.
It's something about this method though. Has to be. If I comment it out, it works great. If I leave it in, Error. But it doesn't throw errors with NSZombieEnabled turned off ever.
The purpose of NSZombieEnabled is to detect messages that get sent to objects after they've been deallocated. The console error you're seeing is NSZombieEnabled telling you that a release message is being sent to a deallocated instance of UIImage. Usually a bug like this is the result of too many calls to release, or not enough calls to retain.
In this case, your scaleImage:toSize: method returns an autoreleased UIImage. The error message you're getting from NSZombieEnabled suggests that you may be releasing this object after it gets returned. This would explain your bug. When your autorelease pool drains it would try to release an object that's already been deallocated.
You're passing imageFromFile to scaleImage:toSize:, and then reassigning that same variable to the return value. There's nothing wrong with this idiom per se, but does require some extra care to avoid memory bugs like this one. You're overwriting your reference to the original object, so you either have to make sure it's autoreleased before the assignment, or save a separate reference that you can manually release after the assignment. Otherwise your original object will leak.
The error was due to a release going on in the makeRoundedCornerImage method from UIImageManipulator. Still not sure why it wasn't getting picked up without NSZombieEnabled turned on, but that's what it was.
You can find the offending line in the Gist I posted in the original question: Line 74.