Advertise here




Advertise here

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Virtual memory leak when using UIImageView setAnimationImages?

higginshiggins Posts: 46Registered Users
edited August 2009 in iPhone SDK Development
Hi everybody,

I'm seeing massive virtual memory usage when using setAnimationImages on a UIImageView. My UIImageView is fullscreen (480x320, landscape mode), and I'm passing it an array of 480x320 PNGs to cycle through, in a simple animation. The app I'm writing is a simple game for kids where you tap the screen, a multi-frame animation plays and stops. It changes the image to the next animal, then you can tap the screen again for another animation. And so on. FWIW the animation is of an animal. I'm also playing sounds (like a moo, etc.).

Every time I run the animation, VM usage goes up by several megs. This would make sense given that I'm using big fullscreen images. But what doesn't make sense is that the virtual memory is never released, and pretty soon Springboard terminates my app (when I get a bit over 80 megs VM usage). Real memory used is about 6 megs, and does not seem to be increasing (no leaks reported by the Leaks tool, anyway). If I comment out the code in animateAnimal below, the VM growth goes away, so I believe that's where the issue is.

I'd love it if somebody could explain why VM usage is going up like this. Am I not releasing something? Is this just what happens when you use setAnimationImages??

Here's my code...

In the view controller .h:
@interface GameViewController : UIViewController {
	UIImageView *animal;
    NSInteger currentAnimal;
}

@property (nonatomic, assign) UIImageView *animal;
@property (readwrite, assign) NSInteger currentAnimal;


In the view controller .m (note I'm not including the code where we increment currentAnimal, but suffice to say, it gets incremented elsewhere):
@synthesize animal, currentAnimal;

- (void)animateAnimal {

	NSString *animalString;
	
	if (currentAnimal == 1) {
		animalString = [[NSString alloc] initWithString:@"cow"];
	} else if (currentAnimal == 2) {
		animalString = [[NSString alloc] initWithString:@"duck"];
	} else if (currentAnimal == 3) {
		animalString = [[NSString alloc] initWithString:@"pig"];
	} else if (currentAnimal == 4) {
		animalString = [[NSString alloc] initWithString:@"cat"];
	}
	
	NSMutableArray *animalFrames = [[NSMutableArray alloc] init];
	
    // Assign PNG frames to animation array, like cow1.png, cow2.png...
    for (int i = 1; i < 12; i++) {
        NSString *cname = [NSString stringWithFormat:@"%@%i", animalString, i];
        UIImage *img = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:cname ofType:@"png"]];
        if (img) [animalFrames addObject:img];
    }

    [animal setAnimationImages:animalFrames];
    animal.animationDuration = 4.0;
	animal.animationRepeatCount = 1;
	[animal startAnimating];

	[animalFrames release];
	[animalString release];
}


And finally, in touchesBegan (where I trigger the animation based on the screen being touched). Note: I'm also firing off some other timers to do other stuff (like play a sound a few seconds later), hence the use of a timer.
[NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(animateAnimal) userInfo:nil repeats:false];


So...I don't get it. It looks to me like I'm using UIImage imageWithContentsOfFile, which is supposed to not cache the image (previously I was using imageNamed, which I hear can gobble memory due to caching)...so I think I've got that covered. I'm releasing the animalFrames and animalString objects. The animal UIImageView is using 'nonatomic, assign' (rather than retain)...which frankly I don't understand, but would seem not to be retaining memory.

Any help?
Post edited by higgins on

Replies

  • KenrikKenrik Posts: 288Registered Users
    edited December 2008
    This is how I did a set of animation images and it works perfect with no problems no mater how many times it runs...
    opacity = [[UIImageView alloc] init];
    	UIImage* opa1 = [UIImage imageNamed:@"o1.png"];
    	UIImage* opa2 = [UIImage imageNamed:@"o2.png"];
    	UIImage* opa3 = [UIImage imageNamed:@"o3.png"];
    	UIImage* opa4 = [UIImage imageNamed:@"o4.png"];
    	UIImage* opa5 = [UIImage imageNamed:@"o5.png"];
    	UIImage* opa6 = [UIImage imageNamed:@"o6.png"];
    	UIImage* opa7 = [UIImage imageNamed:@"o7.png"];
    	UIImage* opa8 = [UIImage imageNamed:@"o8.png"];
    	UIImage* opa9 = [UIImage imageNamed:@"o9.png"];
    	UIImage* opa10 = [UIImage imageNamed:@"o10.png"];
    	UIImage* opa11 = [UIImage imageNamed:@"o11.png"];
    	UIImage* opa12 = [UIImage imageNamed:@"o12.png"];
    	UIImage* opa13 = [UIImage imageNamed:@"o13.png"];
    	UIImage* opa14 = [UIImage imageNamed:@"o14.png"];
    	UIImage* opa15 = [UIImage imageNamed:@"o15.png"];
    	UIImage* opa16 = [UIImage imageNamed:@"o16.png"];
    	UIImage* opa17 = [UIImage imageNamed:@"o17.png"];
    	UIImage* opa18 = [UIImage imageNamed:@"o18.png"];
    	NSArray *imagesOpcaity = [NSArray arrayWithObjects:opa1, opa2, opa3, opa4, 
    	opa5, opa6, opa7, opa8, opa9, opa10, opa11, opa12, opa13, opa14, opa15, opa16, opa17, opa18, nil];
    	[opacity setAnimationImages:imagesOpcaity];
    	[opacity setAnimationRepeatCount:1.0];
    	[opacity setAnimationDuration:0.2];
    	opacity.frame = CGRectMake(0, 0, 260, 480);
    	opacity.center = CGPointMake(190,self.center.y);
    	opacity.image = [UIImage imageNamed:@"levelOpacity.png"];
    	[opacity setAlpha:0.95];
    

    I then just add opacity to the superview and release it as you normally would any item you alloc.

    works for me.
  • higginshiggins Posts: 46Registered Users
    edited December 2008
    Hmm. I wonder if the issue is that I'm synthesizing 'animal,' and never explicitly releasing it. I admit, my memory management kung fu is not sufficient to this task.
  • dpharaohdpharaoh Posts: 4New Users
    edited January 2009
    Higgins, did you end up going one way or another with the animation style, to solve the memory leak/crash? Looks like you're a few weeks ahead of me but I want to do the exact same thing.
  • higginshiggins Posts: 46Registered Users
    edited January 2009
    Hey dpharaoh, I never really solved the underlying problem. It sure looks like an Apple bug to me, though I'm not a good enough programmer to guarantee that.

    I did a bunch of work around the edges and ended up having to ship with the issue still present. What I ended up doing was to change the design of the app so that it would survive for a very long time before the leak got too bad (like many many times through the game, more than is reasonable for any given play session). However, it does still crash if you play it long enough (like over a half hour).

    The primary strategy I used for reducing memory usage was to reduce the frame size of my images. A lot of them were initially 480x320, with a ton of transparent "whitespace" around the actual image content I cared about. Trimming these down to be tightly cropped around the image content I wanted to animate dramatically reduced VM usage -- since the VM is being consumed based on the frame size of the image, not its file size. So for example, I would trim an image down to 240x120 or so, and save a TON of VM, since I was running through a number of frames with each image, and all that transparent space was eating up VM.

    I found these posts helpful, but they didn't really solve my problem:

    Problem dealloc'ing memory used by UIImageViews with fairly large image in an UIScrollView - Stack Overflow

    In CocoaTouch (iPhone OS) how do I find/eliminate leaks that the Instruments Leak tool doesn't find? - Stack Overflow

    Please do let me know if you find a solution to this issue! I'm at a point where I'd like to fix it in my app, but I just don't know what else to do, aside from additional frame trimming (which will only ever go so far).
  • dpharaohdpharaoh Posts: 4New Users
    edited January 2009
    So far my workaround involves using a video player and videos in the code instead of the UIImage/animationImages method for the longer animations, though I still have about 10 of the animationImages style with only a few frames, and it still crashes given enough runs.

    I must say it is thoroughly dismaying, I have spent some weeks finding different methods of loading and releasing images, none completely fixes the problem.

    The next method I plan on trying involves using different views entirely for the animations. Maybe switching views will prompt the iPhone to give up the memory it is holding onto so earnestly from the UIImageViews.
  • sannivsanniv Posts: 20Registered Users
    edited April 2009
    Hello Higgins,

    I am wondering whether your problem is solved or not, because I also facing this issue that my VM consumption goes high and never comes back. So if you have got resolution of your issue than pls reply.

    Regards,
    Sanniv
    higgins wrote: »
    Hey dpharaoh, I never really solved the underlying problem. It sure looks like an Apple bug to me, though I'm not a good enough programmer to guarantee that.

    I did a bunch of work around the edges and ended up having to ship with the issue still present. What I ended up doing was to change the design of the app so that it would survive for a very long time before the leak got too bad (like many many times through the game, more than is reasonable for any given play session). However, it does still crash if you play it long enough (like over a half hour).

    The primary strategy I used for reducing memory usage was to reduce the frame size of my images. A lot of them were initially 480x320, with a ton of transparent "whitespace" around the actual image content I cared about. Trimming these down to be tightly cropped around the image content I wanted to animate dramatically reduced VM usage -- since the VM is being consumed based on the frame size of the image, not its file size. So for example, I would trim an image down to 240x120 or so, and save a TON of VM, since I was running through a number of frames with each image, and all that transparent space was eating up VM.

    I found these posts helpful, but they didn't really solve my problem:

    Problem dealloc'ing memory used by UIImageViews with fairly large image in an UIScrollView - Stack Overflow

    In CocoaTouch (iPhone OS) how do I find/eliminate leaks that the Instruments Leak tool doesn't find? - Stack Overflow

    Please do let me know if you find a solution to this issue! I'm at a point where I'd like to fix it in my app, but I just don't know what else to do, aside from additional frame trimming (which will only ever go so far).
  • higginshiggins Posts: 46Registered Users
    edited April 2009
    sanniv wrote: »
    Hello Higgins,

    I am wondering whether your problem is solved or not, because I also facing this issue that my VM consumption goes high and never comes back. So if you have got resolution of your issue than pls reply.

    Regards,
    Sanniv

    Hi there,

    Actually I think I did solve it. In trying to make my app work with the 3.0 beta I stumbled across a solution to the memory leaks that works across the 2.x versions as well. Here's all it is:
    [myImageView setAnimationImages:nil];
    

    So the deal is, apparently you have to do this:

    1. Pass your UIImageView the array of UIImages to be animated.
    2. Start the UIImageView animating.
    3. Wait for the animation to complete.
    4. Pass nil to the AnimationImages property of the UIImageView. Voila, your virtual memory (that previous array of UIImages from step 1) is released.

    I think there are some other threads here about testing the UIImageView to see if it's currently animating (so you could effectively listen, or at least test for, the end of the animation...and then set the nil value). In my case the animations are pretty rare and very reliable, so after passing in the initial array of UIImages and starting the animation, I just spawn an NSTimer that's set a second or so after my animation is supposed to complete -- this calls a function that sets the nil value, and all seems to work fine.

    If you need any more code bits, just let me know. Hope this helps!
  • sannivsanniv Posts: 20Registered Users
    edited April 2009
    Thanks Higgins,

    I will try your suggestion, lets see I get consumed virtual memory back or not. I will let you know..

    Regards,
    Sanniv.
    higgins wrote: »
    Hi there,

    Actually I think I did solve it. In trying to make my app work with the 3.0 beta I stumbled across a solution to the memory leaks that works across the 2.x versions as well. Here's all it is:
    [myImageView setAnimationImages:nil];
    

    So the deal is, apparently you have to do this:

    1. Pass your UIImageView the array of UIImages to be animated.
    2. Start the UIImageView animating.
    3. Wait for the animation to complete.
    4. Pass nil to the AnimationImages property of the UIImageView. Voila, your virtual memory (that previous array of UIImages from step 1) is released.

    I think there are some other threads here about testing the UIImageView to see if it's currently animating (so you could effectively listen, or at least test for, the end of the animation...and then set the nil value). In my case the animations are pretty rare and very reliable, so after passing in the initial array of UIImages and starting the animation, I just spawn an NSTimer that's set a second or so after my animation is supposed to complete -- this calls a function that sets the nil value, and all seems to work fine.

    If you need any more code bits, just let me know. Hope this helps!
  • sannivsanniv Posts: 20Registered Users
    edited May 2009
  • CashCacheCashCache Posts: 19Registered Users
    edited August 2009
    Hi Higgins,

    I'm curious as to how you got around the lag when the animation first starts. I have 20 full screen images that I am trying to animate, but I am seeing a two second lag from the time I call startAnimating until the images start to animate. Did you have the same problem?

    It's interesting to note that if I re animate the same set of images, they animate instantly. It's just the first time through were I see the lag.

    Thanks,

    -Cash
    higgins wrote: »
    Hi there,

    Actually I think I did solve it. In trying to make my app work with the 3.0 beta I stumbled across a solution to the memory leaks that works across the 2.x versions as well. Here's all it is:
    [myImageView setAnimationImages:nil];
    

    So the deal is, apparently you have to do this:

    1. Pass your UIImageView the array of UIImages to be animated.
    2. Start the UIImageView animating.
    3. Wait for the animation to complete.
    4. Pass nil to the AnimationImages property of the UIImageView. Voila, your virtual memory (that previous array of UIImages from step 1) is released.

    I think there are some other threads here about testing the UIImageView to see if it's currently animating (so you could effectively listen, or at least test for, the end of the animation...and then set the nil value). In my case the animations are pretty rare and very reliable, so after passing in the initial array of UIImages and starting the animation, I just spawn an NSTimer that's set a second or so after my animation is supposed to complete -- this calls a function that sets the nil value, and all seems to work fine.

    If you need any more code bits, just let me know. Hope this helps!
  • higginshiggins Posts: 46Registered Users
    edited August 2009
    I'm curious as to how you got around the lag when the animation first starts. I have 20 full screen images that I am trying to animate, but I am seeing a two second lag from the time I call startAnimating until the images start to animate. Did you have the same problem?

    Hey there,

    I actually don't see any lag, though my images are a bit smaller than yours. When you construct the image array, are you using:
    [UIImage imageWithContentsOfFile:filename]
    

    OR are you using...
    [UIImage imageNamed:filename]
    

    ?

    For all my animations, I'm using the latter, since I found that it's much faster to load the images (and based on various chatter on these boards, the iPhone OS does some funky caching BS which you may not like...but I believe releasing the array after the animation actually fixes this).

    I'm also only doing a four-frame animation spanning 1.7 seconds, so perhaps it has something to do with the number of images in your array and/or the speed at which they need to display.

    Another possibly thought is that I wonder *when* you're building the array versus starting the animation. Are they happening at the same time? Like, user taps button, you build this 20-image array, then you start animating? If so, maybe try building the array earlier (possibly in a thread?) so you can take that speed hit at a time prior to needing the animations to run. ?

    Let me know if any of this helps!

    ;Chris
  • CashCacheCashCache Posts: 19Registered Users
    edited August 2009
    Hi Chris, and thanks for the reply.

    I am overlaying images on top of one another - some of which have transparent sections. I am then aniating multiple UIImageViews at the same time. I am using the following code to load my arrays:
    [UIImage imageWithContentsOfFile:filename]
    

    One of my UIImageViews and its array stay around for as long as the app is alive. However, the other one is loaded on demand and as soon as it is done, I release it.

    I am sure the lag is not coming from the loading of the array and can see that it comes on the first call to startAnimating. I've been able to get around this by quickly animating the view while it is off screen. Then animate it when I need it - this gets rid of the lag, but seems like a silly work-around.

    The reason I am not using the other method of loading the images is because, after reading the docs, it appears I lose control over the cache and I won't be able to free it completely on demand. Since I am constantly animating, releasing, loading a new animation and doing this over and over, I need to make sure I can give back memory as soon as I am done.

    If you have any other ideas, I'd love to hear them. Otherwise I will just keep plugging away.

    Thanks again...

    -Scott
  • higginshiggins Posts: 46Registered Users
    edited August 2009
    Hey Scott,

    That does sound like an obnoxious workaround (animating offscreen), though I suppose if it works, what the hell.

    All that springs to mind is whether you've run setImage on your UIImageView at all prior to the animation, to make sure it's fully initted and stuff? Like passing it a transparent PNG or something? I'm just wondering if, perhaps lacking an initial setImage or initWithImage or whatever it is, upon your first animation run it might spontaneously decide to load your 20 images from disk or something. Anyway, probably not a solution, but all I can come up with.

    The other thing I'd suggest is just trying the UIImage imageNamed bit, just to see if it clears up the lag somehow. If it does, then you've just got a memory management problem, which is a whole different hellish adventure. :)

    ;Chris
  • CashCacheCashCache Posts: 19Registered Users
    edited August 2009
    Hi Higgins,

    It's me again! As I bounce around the web Googling for a solution, I keep bumping into this post, so I want to ask you:

    Are you sure this fixed your memory problem in 2.2.1?
    [myImageView setAnimationImages:nil];
    

    This seems to do the trick with 3.0, but am still seeing VM climb through the roof as my app runs in 2.2.1. It sure looks like a bug in UIImaveView that was fixed in 3.0 but still there in 2.2.1.

    Any ideas on how to fix this?

    Thanks again,
Sign In or Register to comment.