I'm wondering under which circumstances this code breaks in the second assert. In other words, when can -[UIImage CGImage] return nil? The documentation isn't very telling here.
- (void)setImage:(UIImage *)anImage {
assert(anImage);
CGImageRef cgimage = anImage.CGImage;
assert(cgimage);
}
I'm pretty sure, the UIImage is correct since it's fetched from the app bundle. So far I haven't been able to reproduce the case but I do see some user crash reports.
One more possibility that I've come across in the docs:
If the UIImage object was initialized using a CIImage object, the value of the property is NULL.
Another possibility is that you have multiple threads accessing the same UIImage object at the same time; this was happening in my code with the same symptom.
That might explain your sporadic crash reports, too, since the access patterns would depend on timing that changes from run to run.
To the best of my knowledge, if the first assert passes(showing that anImage is not nil) it means that it could not load the image. Check to make sure that the image is being copied into your bundle.
A case where anImage would be non-nil but cgimage would be nil could be contrived as follows:
UIImage* myImage = [[UIImage alloc] initWithCGImage:nil];
As previous answers have indicated there are other ways one could find themselves in such a scenario.
Related
I'm creating an UIImage like this:
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
Am I responsible for releasing this?
No.
Return Value
An autoreleased image
object containing the contents of the
current bitmap graphics context.
(doc link)
Read the documentation here. As it's written there, it returns an autoreleased object. You don't have to release it. If you want to keep it, you need to retain it somehow.
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.
I wrote some naive code(in the sense that it's synchronous calls) for a tableview that contains thumbnails of images pulled from a url. The code in cellForRowAtIndexPath that pulls the image goes like this:
data = (data == nil)? [[NSData alloc] initWithContentsOfURL:photoThumbPage] : [data initWithContentsOfURL:photoThumbPage];
thumbImg = (thumbImg == nil)? [[UIImage alloc] initWithData:data] : [thumbImg initWithData:data];
I felt that there might be a speedup with not allocating a new nsdata and uiimage each time, so I defined them in my class. Each time I get a thumbnail, I want to check if they are defined, and if they already are, I just initialize them again with different values.
As I am writing this question here, you can already guess that all sorts of bad things happenned:) When I scroll through my tableview, sometimes, two or more entries will share the same image, and I get some errors in console that do not crash my app, but tell me that my jpeg file is corrupted.
Putting aside the fact that each of these requests should be asynchronous, and that I really should be caching these results, can anyone tell me where I might be tripping up? It seems that if cellForRowAtIndexPath is being called for each cell that is visible, and my code is synchronous, there should be no reason why the nsdata and uiimage variables should persist over the calls(they are wiped out by the init methods). I swapped out these member variables and initialized them each time:
NSData *data_local = [[NSData alloc] initWithContentsOfURL:photoThumbPage];
UIImage *thumbImg_local = [[UIImage alloc] initWithData:data];
and it works just fine. Is there any merit in allocating a reusable member variable rather than allocating a new NSData and UIImage each time i want to load a thumbnail? Or is that just a recipe for disaster? Thanks for any comments/help.
The issue is not that you are reusing member variables but that you are reusing the same space in memory. When you call init on data the second time, you are still using the same memory space as before (the space that came from the alloc method). This results in more than one UIImage pointing to the same area of memory, hence the multiple images, and the underlying data changing from underneath each UIImage when a new one is created, hence the error messages about bad jpegs.
I'm writing an application which is quite graphically heavy and therefore I'm trying to implement a caching mechanism within my view controller that creates a view once, and retains it for future use, similar to the following:
- (UIView *)logoView
{
if(_logoView == nil)
{
_logoView = [[UIImageView alloc] initWithImage: [UIImage imageNamed: #"logo_header.png"]];
_logoView.contentMode = UIViewContentModeCenter;
}
return _logoView;
}
This all works fantastically, however, I'm getting a bit confused over a certain issue; when I simulate a memory warning I call a method which purges the cache, like so:
- (void)purgeCachedContent
{
if(_logoView != nil)[_logoView release];
}
However, this seems to be causing my application issues, as it can get called several times (dealloc, didReceiveMemoryWarning and viewDidUnload).
How can I determine if an instance exists to send it a release message? Even when I try to NSLog a released view I receive a EXC_BAD_ACCESS error, so I'm having trouble figuring out the best way I'd do it. I even removed the if statement hoping to rely on being able to send nil objects messages, but this causes the same error...
I'm sure it's something simple missing from my objective-c knowledge... I just don't know what!
_logoView isn't set to nil automatically just by releasing it, so any future methods you try to call using that pointer will go to a memory location that used to contain a valid object, but now contains junk. You can fix this by setting _logoView = nil; after releasing it.
If you want to cache it why would you want to release it?
just use autorelease on init:
_logoView = [[[UIImageView alloc] initWithImage: [UIImage imageNamed: #"logo_header.png"]] autorelease];
I am wondering what the most efficient way is to make a CALayer with an image in it.
I know you can load a UIImage and then call [image CGImage] but is this the best way of going about it? As far as I can tell from Apple's documentation this is the only way you can do it.
Try this:
layer.contents = (__bridge id)[ uiimage CGImage ] ;
HTH
Well, a CGImage isn't a CALayer, so you are obviously leaving some steps out there, but I assume you know what you're talking about, as far as drawing to a CALayer or whatnot.
If your question is about creating CGImages without using a UIImage, you can try looking into the following functions:
CGImageCreateWithJPEGDataProvider
CGImageCreateWithPNGDataProvider
If you happen to know beforehand what sort of image it is, just use the appropriate method. Otherwise, you'd need to look at the file signature to see if it contains PNG or JFIF. Of course, this requires you implement a CGDataProvider.
I assume this is exactly what the UIImage class is already doing. If you want to squeeze out every bit of efficiency from that, you can use one of the following methods in the UIImage class:
+ (UIImage *)imageWithContentsOfFile:(NSString *)path
+ (UIImage *)imageWithData:(NSData *)data
The only difference between these methods and
+ (UIImage *)imageNamed:(NSString *)name
is that imageNamed: caches the image. If you use one of the first methods, it does not.