Phil John
Phil John

Reputation: 1237

iOS memory usage increases until crash

I have an iOS application that uses a UINavigationController to move forwards (push) and backwards (pop) in a fairly simple master/detail fashion.

The detail view controller has 2 or 3 full screen images that I add manually using

layer1 = [[UIImageView alloc] initWithFrame:layer1Rect];//layer1rect is a CGRect
UIImage *img1 = [[UIImage alloc]initWithContentsOfFile:imgName];//imgName is an image in the bundle
layer1.image = img1;
layer1.alpha = 1;
[self.view addSubview:layer1];
img1 = nil;

In my viewDidUnload I have

for (UIView *subview in [self.view subviews]) {
        [subview removeFromSuperview];
}

layer1 = nil;
layer2 = nil;
layer3 = nil;

The problem I have is that after pushing and popping a dozen times, I get a memory warning and a crash.

I'm using ARC, though from what I've read elsewhere that's unlikely to be the source of the problem in itself.

When I run the app in instruments (on the device, rather than the simulator) I see some interesting results.

First of all, the memory allocation tool shows less than 2MB of memory usage and no leaks, right up to the warnings and the crash. I can also see that my instance of the detail view controller gets released when I do the pop.

However, if I look at the Activity Monitor I see the usage start at around 50MB and continue to increase every time I push to the detail view controller until it crashes at around 250MB.

I thought that maybe the images were being cached, but I'm not using imageName: anywhere, which is a common cause of caching.

So what am I missing? How can I ensure that when I pop the detail view controller off the stack that all of the memory it was using is made available again?

Upvotes: 0

Views: 1113

Answers (1)

Tommy
Tommy

Reputation: 100622

viewDidUnload is called if your controller's view is evicted from memory due to a low-memory warning. Dropping the view and loading it again later was how UIViewControllers dealt with low-memory warnings under iOS 2–5.

Under iOS 6 the view controller never drops its view. So you never get viewDidUnload. In your case that'll mean you add another UIImage every time the first block of code is running (I assume it's not in viewDidLoad?). The old one won't be deallocated because it has a superview; that you're releasing your reference to it makes no difference.

Furthermore, the initWithContentsOfFile: would be better expressed as [UIImage imageNamed:] as the latter explicitly caches the images whereas the former reloads from disk every time, creating a new copy of the pixel contents.

So the recommended changes are to change this:

layer1 = [[UIImageView alloc] initWithFrame:layer1Rect];

To this:

[layer1 removeFromSuperview];
layer1 = [[UIImageView alloc] initWithFrame:layer1Rect];

If you have an existing layer1 in your view hierarchy that'll ensure it is deallocated when you implicitly release your reference to it on the initWithFrame: line.

Also:

UIImage *img1 = [[UIImage alloc]initWithContentsOfFile:imgName];

Would be better as:

UIImage *img1 = [UIImage imageNamed:imgName];

As then multiple uses of the image with that filename throughout your app will definitely all point to the same instance, but it'll still be removed from memory if warnings require it.

As for diagnosis, try turning on NSZombies. NSZombies causes deallocated objects to remain alive and is normally used so that you can catch uses of dangling pointers. In this case what you could do is turn on zombies and see whether it changes your memory footprint substantially. If not then that confirms that you're not actually deallocating things.

Activity Monitor is not necessarily a reliable measure — the iOS frameworks use NSCaches where appropriate and neither OSes bother taking memory away from a process if there's nobody to give it to, as that would just be a pointless expenditure of processing. You should use Instruments, especially as it can break down what is in memory by type and count, letting you know not just your total count but what you're spending memory on.

That's the same logic that explains why imageNamed: is usually a better choice despite your comments. Images you have previously loaded remain in memory for as long as there is spare memory to house them. They don't stay around if memory warnings are received and nobody is using them. Only if your responses to memory warnings are really expensive need it be a concern, and usually it helps avoid memory warnings by ensuring that reused resources have the same identity rather than being copies.

Upvotes: 1

Related Questions