Releasing UIImage imageNamed - objective-c

When I use UIImage imagenamed: should I set the variables which hold UIImages to nil before exit? I notice that sometimes when I switch between views which have UIImages, the memory keeps growing and growing with each switch back and forth.

Setting variables to nil is not necessary.
Setting properties to nil (self.property = nil;) will release them if they're declared #property (retain).
Since +imageNamed: doesn't start with "alloc", "copy", "new", or "retain", you don't have to release it. It's possible things are staying in memory because the space isn't needed. Are you seeing any leaks or just memory usage?

Setting UIImage variables to nil won't do anything particularly useful. Also, you shouldn't release the image returned from +imageNamed:, since the method name does not imply that you have ownership of the returned object.
Cocoa maintains an image cache. Subsequent calls to imageNamed: will return the same UIImage object if it is loaded already (since UIImage objects are immutable), otherwise it will load it again into the cache. The lifetime of the image in the cache is up to Cocoa to decide. In low-memory situations the image data may be purged. Even though the actual internal image data is purged from the cache, the object that you have is still able to reference the image (Cocoa will reload the image data if it was purged from the cache). It is explained throughout the UIImage documentation.
If your memory usage is consistently growing then the leak is probably coming from somewhere else.

Related

UIImage causing memory leaks

Instruments is telling me that alot of memory is being allocated when I rapidly set the image name of a UIImageview in my app. I have a UIImageView that changes its image name every frame in my game. When profiled with zombie checking in instruments, the app seems to be constantly gaining live bytes at an enourmous rate. Is there a way that I can deallocate the UIImageView's current image to stop it from doing this? I am using ARC.
My code to assign the UIImageView's image is as follows:
aPlanet.image = [UIImage imageNamed:tempPlanetName];
Where aPlanet is the UIImageView and tempPlanetName is the name of the image. This is called every frame.
[UIImage ImageNamed:] method loads the image into image view and adds this newly created uiimage object to autorelease pool. To get rid of this problem you should use -
NSString *imgPath = [NSBundle mainbundle] pathForResource:#"imageName" ofType:#"png"];
aPlanet.image = [[UIImage alloc] ]initWithContentsOfFile:imgPath];
if you are using arc then you don't need to bother about releasing this newly allocated object of uiimage which was created using initWithContentsOfFile: method.
When you use UIImage imageNamed: it will load and cache that image file. This is intended for reuse of icons and other image resources that will be utilized more than once in your application.
Apart from it seeming somewhat unusual to update an image view with a new image every frame, you should look into alternative means of loading images that you will not need more than once - or even if you do, when you need more control over its lifecycle.
For example have a look at UIImage imageWithContentsOfFile: (documented here: Apple Developer Library Reference). It explicitly states that this method will not do any caching of the image contents.
I hope this helps you, but for every frame I doubt that your performance will be good enough with this approach, but this is probably the topic of a different question should the need arise.

ARC: Memory does not get reclaimed?

I am working on an iPad (only) app and I stumbled across a weird problem. The app gets terminated after a memory warning on iPad 1 but works fine on iPad 2.
I am using ARC and targeting iOS 5. I use nibs and most of my assets are displayed using UIImageViews. I also have a few hundred buttons and plenty of gesture recognizers... I re-watched the WWDC11 videos (sessions 323 and 322) on ARC and I don't seem to be doing anything special.
The app is UIImage intensive, I am doing lots of animations using UIImage. I am however using the initWithContentsOfFile constructor rather than the imageNamed call. I'm trying to prevent the images from being cached by the system.
I'm also using GCD to schedule sound effects and to animate views. I'm always doing this on the main thread.
The app uses a UINavigationController that never has more than 1 UIViewController on it's stack. I can confirm that this is true because the didReceiveMemoryWarning only gets called on the current view controller (I'm logging the call).
The thing I don't understand is why Instruments is reporting high numbers (as if the view controllers don't get deallocated) in both the Allocations and VM Tracker instruments. The Allocations instrument shows a small drop when I navigate from one view controller to another (which is what I expect) but the VM Tracker Instrument shows that the Dirty Size is not dropping when I do the same thing. Eventually the app uses too much memory and gets terminated (on iPad 1). When I get memory warnings on the iPad 2 the app does NOT get terminated though...
It feels as if my images, sounds or views don't get destroyed and the memory does not get reclaimed... My object hierarchy is very basic and there should not be any retain cycles of any sort. I don't even have simple delegates...
Do you have any suggestions? I really don't want to release this app only for the iPad 2 or newer... It's an app for kids and it would be a pitty... I'd be so much happier to learn that I'm doing something wrong, as I really want to make sure this app is the best it can be...
Cheers,
Nick
There are ways to say, 'optimise' your objects by setting their properties to nil when certain things aren't needed -- so while you can't write a dealloc method anymore, you can do self.object = nil (when pertinent) which ends up doing something like this in a non-ARC world for an 'retain' (i.e., strong) property:
- (void)setObject:(id)newObject
{
[object release]; // send release message to current object
object = newObject; // set reference to newObject
[object retain]; // send retain message to newObject
}
Now while in ARC you don't/can't write retain/release yourself in your code, the compiler inserts these calls for you, meaning that in practise, setting a property to nil would do this in the above example:
[object release]; // send release message to current object
object = nil; // set reference to nil
[object retain]; // send retain message to nil (no effect)
Moreover, this is just the tip of the iceberg -- you should make sure that there are no retain cycles in your code, which could be resulting in objects leaking without recourse to their destruction. This means, that there may be places where you're using strong references to a property (i.e., an object), when you should be using a weak property. The difference being, that strong references are retained, and weak references are assigned, the former having its retainCount incremented and the latter resulting in a property assignment that looks like this if handwritten:
- (void)setObject:(id)newObject
{
object = newObject;
}
I don't like answering my own question but I figured it could be helpful to future googlers. I implemented my own UIImage based animation and I no longer use the animationImages property. Turns out my memory issues are now gone as I no longer need to store all the images in memory and I load them as they are required using a timer.
I actually think that rolling out a custom animation is beneficial since it allows for callbacks and more powerful customisation options.
Once I'm happy with it and I feel like it's ready to be shared I will post the class(es) on GitHub.

NSImage size problem

I'm using the same image resource in two different controllers. In both controllers the image is shown in different sizes, the problem is that once the image is shown in a smaller size than the original, the next time I get the image by [NSImage imageNamed:#"resource.png"] the image size is set to the last size it took. I tried by invoking the recache method on NSImage and also tried to set the cache mode to any the posible value, but it didn't work.
Any ideas?
You should never modify an instance of NSImage obtained from imageNamed:. The returned instance is shared with other clients, so it should not be changed.
If you have to setSize: on the image, just make a copy and use that one:
NSImage *image = [[[NSImage imageNamed:#"foo.png"] copy] autorelease];
[image setSize:(NSSize){128, 128}];
The thing is that
[NSImage imageNamed]
As you mentioned is in the cache, and as long as it is in the cache it will return the cached image so what you need to do is first released the previous reference or use the object's setName method and setting to nil. Here is the documentation reference:
The NSImage class may cache a reference to the returned image object for performance in some cases. However, the class holds onto cached objects only while the object exists. If the image object is subsequently released, either because its retain count was 0 or it was not referenced anywhere in a garbage-collected application, the object may be quietly removed from the cache. Thus, if you plan to hold onto a returned image object, you must retain it like you would any Cocoa object. You can clear an image object from the cache explicitly by calling the object’s setName: method and passing nil for the image name.

Why does instruments not find this leak?

Quick one folks. Have a quick look at the code snippet below. I alloc the UILabel *textLabel but I don't release it (commented out). When the method ends, I lose reference to the pointer so it leaks.
Thing is that XCode Instruments doesn't find this leak and I would have thought that it is quite a straightforward case for it to find it. Its not reporting any leaks in my application and yet I found this one myself and its made me a little suspicious.
Am I over looking something or is Instruments rather poor at detecting leaks?
-(UITableViewCell*)newReadOnlyCellWithTitle:(NSString*)title andText:(NSString*)text {
UITableViewCell *cell=[[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease];
cell.textLabel.text=title;
cell.selectionStyle=UITableViewCellSelectionStyleNone;
cell.backgroundColor=[UIColor whiteColor];
cell.opaque=YES;
UILabel *textLabel=[[UILabel alloc] initWithFrame:CGRectMake(80, 11, 350, 24)];
textLabel.text=text;
textLabel.textColor=[UIColor lightTextColor];
textLabel.font=[UIFont fontWithName:STANDARD_FONT size:16.0];
textLabel.opaque=YES;
textLabel.backgroundColor=[UIColor clearColor];
[cell.contentView addSubview:textLabel];
//[textLabel release]; //<== without the release this should leak, yep?
return cell;
}
Edit: output from static analyizer...
Instrument's leak detection works by conservatively scanning memory, looking for pointers and building a graph of connections between allocations. If it finds any pointer(s) to an object in memory that can be reached from a global variable or stack variable, then that object cannot be considered leaked.
Instruments does not know the layout or context of a pointer. If you were to malloc(1024) and there happened to be a bunch of pointers in that [recycled] chunk of memory, those would count even though you will never treat those pointers as real references again.
So, no, Leaks can never be 100% accurate. As well, there are far more many ways to leak memory than an actual leak. If you had a global cache, like this:
NSMutableDictionary *myGlobalCache;
And you were to fill that cache but never prune it, that would be an effective memory leak and there is no way that it'll every show up in Instruments.
I wrote up an in depth discussion of Heapshot analysis, which is related and may be of interest.
Let me guess, -newReadOnlyCellWithTitle:andText: gets called by your -tableView:cellForRowAtIndexPath: method when cell == nil?
The thing is, those cells don't get released, they are cached and pulled back by -dequeueReusableCellWithIdentifier:. Table view uses same cells over and over again thus reducing the need to constantly allocate and release memory.
To make sure this is the case, you can try switching to different view controller, then simulating memory warning in simulator. This will force release on previous controller's view and as a result on cells in discussion. You should notice a leak in Instruments.
I think u will see it only when there are no valid references.
So, in your case, there is still a valid reference that goes something like, (probably) Screen -> table -> cell -> label. Looks like there is a possibility that your screen, the primary view, is still in memory, and hence still a valid reference.
You might want to check the objects that are allocated and then not released and still stay in the memory as valid objects. Checking for leaks isn't always enough.
Leaks will only find objects that are not referred to by any other object. For example if you create two objects in a method that refer to each other but no one else refers to either of them they are both technically leaked objects but leaks can see them since they each have an object that refers to them. Static analyzer is pretty good at finding these kinds of things though so the two in concert should kill almost any leak.
Your missing that there are 2 pointers to the object
Locally you have
UILabel *textLabel=[[UILabel alloc] initWithFrame:CGRectMake(80, 11, 350, 24)];
And within the windowing system which will manage it for you (it is my understanding it is not making a copy, but taking ownership)
[cell.contentView addSubview:textLabel];
So release is unnecessary, and with my limited knowledge even bad.
//[textLabel release]; //<== without the release this should leak, yep?

CGImage, NSArray and Memory

This is a multiple part question, mostly because my ignorance on the matter has multiple layers.
First, I put together a caching system for caching CGImageRef objects. I keep it at the CGImageRef level (rather than UIImage) as I am loading images in background threads. When an image is loaded I put it into a NSMutableDictionary. I had to do a bit of arm twisting to get CGImageRef's into the array:
//Bunch of stuff drawing into a context
CGImageRef imageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
[(id)imageRef autorelease];
[self.cache setObject:(id)imageRef forKey:#"SomeKey"];
So, as you can see, I'm trying to treat the Image Ref as an NSObject, setting it to autorelease then placing it in the dictionary. My expectation is this will allow the image to be cleaned up after being removed from the dictionary. Now, I am beginning to have my doubts.
My application clears the cache array when the user "restarts" to play with different images. Running the application in Instruments shows that the memory is not dropping back to the "start" level on restart, but instead remains steady. My gut tells me that when the array has all objects removed the CGImageRef is not being cleared.
However, I'm unable to confirm this as I don't quite know how to track down the actual source of the memory in instruments. It's just a list of (Malloc 16 Bytes, Malloc 32 Bytes, etc), drilling into them just show a list of dyld callers. Not sure how to properly read it.
So, first question, is my way of caching CGImageRef objects completely flawed? And is there a better way to confirm such things in instruments?
First of all, caching CGImages is OK and I don't see any problems with the code you posted.
Am I correctly assuming you use an NSMutableDictionary as the cache? If so, you can clear it by sending it -removeAllObjects, which should release all the keys and values. If you just set different images for the same keys, memory usage may remain roughly the same because you replace previous images with new ones. If the images have the same size, memory usage should be constant except brief spikes when you create a new batch of images.
As for Instruments, I've seen it both report false positives and miss real leaks. Try running it several times, making pauses, if possible, for the Leaks instrument to "catch up". This sounds crazy, but I think it may make it a bit more reliable.
If all else fails, you can log the contents of the cache before and after loading a set of images to make sure the cache itself works as expected.
Why not just cache UIImage objects; you can make them fine on a background thread?
It's UIImageView objects that you have to be more careful with and even they are OK for most operations in the background.