Removing CAKeyframeAnimation doesn't release the memory - iphone

I'm adding a CAKeyframeAnimation to the layer of an ImageView for representing the animation of an image:
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
animation.values = arrayOfImages;
[animation setRemovedOnCompletion:YES];
animation.delegate = self;
[animation setValue:delegate forKey:#"AnimatedImageViewDelegate"];
animation.repeatCount = repeatCount;
animation.fillMode = kCAFillModeForwards;
animation.calculationMode = kCAAnimationDiscrete;
[animation setValue:animationName forKey:#"name"];
When I want to start the animation I call:
animation.duration = duration;
[self.layer addAnimation:animation forKey:animationName];
[self.layer setContents:[animation.values lastObject]];
When the animation finishes, I want to completely remove it and release its memory.
[self.layer removeAllAnimations];
[self.layer removeAnimationForKey:animationName];
Doing this, and using Instruments-Activity Monitor, the memory is not relased unless I release the complete imageView.
How can I release that animation memory without destroy the UIImageView???

You are using the convenience method to instantiate the object. So you will not have control over when the object is actually released. To have control over that, you will need to alloc the object and dealloc it yourself:
Create the instance:
CAKeyframeAnimation *animation = [[CAKeyframeAnimation alloc] init];
Then after the animation finished call after your remove lines:
[animation release];

First off, if this is an iOS 5 project ARC should be handling your releases for you - which is really nice!
Now -- I used a lot of animations in my project and noticed similar bursts in the heap. My guess is that CA caches a lot of what is animated internally. To confirm those items are actually being autoreleased I recommend setting up a test for allocations or leaks with the instruments tool and do the following:
Determine a situation where you would trigger and the animation.
Mark the heap after the animation completes.
Trigger something that would take you to another view where the objects should have been removed.
Repeat the process in steps 1-2-3 as many times as you want. I normally do it about 10 times or so.
That's it.. basically animate - mark - undo - animate - mark. You should see the heapshots decreasing to 0 or almost 0 if CoreAnimation is autoreleasing objects correctly!
For more info on how to test like this take a look at this article:
http://www.friday.com/bbum/2010/10/17/when-is-a-leak-not-a-leak-using-heapshot-analysis-to-find-undesirable-memory-growth/

I have same problem about memory with CAKeyframeAnimation when set values = array of images.
And i see that the problem i get is that the images are inited by [UIImage imagewithname:] which is cached to memory by default. So my solution is change to use [UIImage imageWithContentsOfFile:]
Hope this helpful.

In my case the CAKeyframeAnimation added to an imageView and container view is not getting released even that its not on the view hierarchy.
It turned out that the CAKeyframeAnimation delegate was holding it. I just removed the delegate:
beatAnimation.delegate = nil

Related

Schedule a low-priority task on the main thread

I have a drawRect method that is rather slow (100-200ms). To save time, I need to cache the results. I am doing the actual caching like this:
// some code to check if caching would be desirable goes here. If it is desirable, then
UIGraphicsBeginImageContext(viewSize);
CGContextRef c = UIGraphicsGetCurrentContext();
[view.layer renderInContext: c];
UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
[self.cachedImageArray addObject:image];
UIGraphicsEndImageContext();
The caching itself can take up to 40ms. This is still easily worth it. But the caching has to wait until everything is rendered, or it will come out wrong. Additionally, the caching is a low-priority task. Once everything is displayed, it is possible that other things will still be going on, and if so, the caching can wait. But since it uses UIKit, it has to be on the main thread.
Rather than putting in some arbitrary delay, is there a bulletproof way to wait like this?
The caching itself doesn't have to be done on the main thread. You can get a copy/reference of the image context or bitmap data, and launch it using an NSThread only when the rendering is done. Example:
- (void) drawRect:(CGRect)rect {
do_rendering_here();
// when rendering completed:
NSThread *t = [[NSThread alloc] initWithTarget:self selector:#selector(doCaching:) object:c];
[t start];
[t release];
}
- (void) doCaching:(CGContextRef)ctx {
// do whatever kind of caching is needed
}

Keeping a Core Animation transition persistent?

I have a custom UILabel that I want to add a transition to when the text changes.
I create a transition:
CATransition *a = [CATransition animation];
a.duration = 1.0;
a.type = kCATransitionPush;
self.transition = a;
I set the transition to a property in my class. Here's the method I call to update the text:
- (void)postMessage:(NSString *)message {
[self.messageLabel.layer addAnimation:self.transition forKey:#"statusAnimation"];
self.messageLabel.text = message;
}
This all works fine. However, I don't want to keep adding the animation to my label. That slows it down every time I update.
My question is - how can I keep this transition persistent so it shows an animation every single time I call this method without having to add it manually every time?
What you're asking isn't quite possible. Core Animation is a stateful library, and copies the animation object you present, then releases it upon completion. If you are having issues with performance, you should be using the animation block methods. This overview of Core Animation with blocks should help you get started.

Redrawing GameHUD multiple times causes cocos2d frame rates to drop significantly

I'm currently building a game for the iPhone with cocos2d and have the following problem:
I have a singleton class called GameHUD which displays a HUD in front of the current level scene. Once in a while I want the HUD to be redrawn, so it changes accordingly to the current game status. The problem is the more often I redraw the HUD, the more the frame rate drops.
I'm guessing that I fail to release some ressources, but cannot figure out which ones I have to release. (I'm pretty new to memory management.. I understand that I have to release objects that are created with one of following keywords: "new", "alloc", "copy" or "retain". But cocos2d mainly generates autorelease objects, therefor I mustn't release them manually.. correct me, if I'm wrong ;))
//static, so it can be called from other classes
+(void)redrawGameHUD{
CGSize winSize = [CCDirector sharedDirector].winSize;
//get reference to background-sprite
CCSprite *background = [[[GameHUD class] sharedHUD] towerBackground];
//remove the child from the HUD, if it exists
[[[GameHUD class] sharedHUD] removeChild:background cleanup:YES];
//create sprite containing the background-image
background = [CCSprite spriteWithFile:#"background.png"];
//add background image to HUD
[[[GameHUD class] sharedHUD] addChild:background];
//load images that should be displayed into an array
NSArray *images = [NSArray arrayWithObjects:#"image1.png", #"image2.png", #"image3.png", #"image4.png", nil];
//remove sprites from HUD before drawing them again.
//the "buildable" array contains all those already drawn sprites
for (CCSprite *entity in [[[GameHUD class] sharedHUD] buildable]) {
[[[GameHUD class] sharedHUD] removeChild:entity cleanup:YES];
}
[[[[GameHUD class] sharedHUD] buildable] removeAllObjects];
//loop over sprites, initialize them and add them to the HUD
for(int i = 0; i < images.count; ++i) {
NSString *image = [images objectAtIndex:i];
CCSprite *sprite = [CCSprite spriteWithFile:image];
//add sprite to HUD and memorize them in the "buildable" array
[[[GameHUD class] sharedHUD] addChild:sprite];
[[[[GameHUD class] sharedHUD] buildable] addObject:sprite];
}
}
So every time this method is called, the frame rate drops a little bit and stays down.. Can somebody please tell me, what I am doing wrong? Thanks.
Try not to create and remove sprites at runtime, ie try to avoid doing this frequently:
[CCSprite spriteWithFile:#"background.png"];
This allocates new memory for the sprite, and theres quite a bit of things going on behind the scenes when you create a new sprite. And of course you're releasing the already existing sprites. All of that is unnecessary.
In your redrawGameHUD method I see no indication why you actually want to create the sprites anew. The sprites are using the same images every time. So why not just keep the old ones? Unless you edited the code before you posted it in the questions, there's no need to remove and re-create the HUD sprites.
You also might want to create a texture atlas for all the HUD sprite images. For one, you can then render all HUD sprites with one draw call by using a CCSpriteBatchNode. Secondly, whenever you do want to assign a new texture to an existing sprite, you would simply change the CCSpriteFrame of that sprite instead of throwing away the sprite and re-creating it.
Something else that bothers me, you keep writing this:
[[[GameHUD class] sharedHUD] addChild:sprite];
First, this is the same as writing the following, the message to class is absolutely unnecessary (makes me wonder where you picked that up?):
[[GameHUD sharedHUD] addChild:sprite];
And since you do this multiple times over, you should keep a temporary local copy of GameHUD, this again removes several unnecessary Objective-C messages:
GameHUD* gameHUD = [GameHUD sharedHUD];
// from now on use gameHUD instead of [GameHUD sharedHUD]
[gameHUD addChild:sprite];
This is particularly good advice for loops, because doing this:
for (CCSprite *entity in [[[GameHUD class] sharedHUD] buildable])
will send two extra messages (class and sharedHUD) for every entity in the buildable array. Those extra calls can quickly add up, although they're certainly not enough for the drop in framerate you're experiencing.
You also unnecessarily keep all the HUD sprites in the "buildable" array. Why not use the already existing children array that cocos2d uses? Simply add each HUD sprite that is "buildable" with the same tag, for example 123.
[gameHUD addChild:sprite z:0 tag:123];
If you need to do something with all the "buildable" sprites you can then iterate over the children like this:
CCNode* node;
CCARRAY_FOREACH([gameHUD children], node)
{
if (node.tag == 123)
{
CCSprite* buildable = (CCSprite*)node;
// do stuff with buildable sprite ...
}
}
Again, this avoids the unnecessary adding, retaining, removing and releasing of objects in the buildable array. And you can be sure that you don't accidentally remove sprites from the node hierarchy but not the buildable array, or vice versa.
I'd like to conclude with an assumption: in your code I saw a general tendency that you're doing many extra things unnecessarily. So I'm guessing this is the case throughout the project. You might want to go back and ask yourself (better: find out) what other things there are that you're having the device perform that are rather unnecessary.
I am 90% sure that you are testing this on the iPhone simulator, am I right?
If you are, then keep in mind that you can't profile an OpenGL app properly on the simulator .. The performance is too variable.
Test it on a real device and check the frame rate again.
If not, then you got a problem somewhere else. All the processing done in this method are trivial, unless your images are 5000x5000 px or something..

Is it better to autorelease or release right after?

There are a lot of cases in which one would alloc an instance, and release it right after it's being assigned to something else, which retains it internally.
For example,
UIView *view = [[UIView alloc] initWithFrame...];
[self addSubView:view];
[view release];
I have heard people suggesting that we go with autorelease rather than release right after.
So the above becomes:
UIView *view = [[[UIView alloc] initWithFrame...] autorelease];
[self addSubView:view];
What's the best practice here? Pros and cons?
In most cases, it wont really matter either way. Since -autorelease simply means that the object will be released at the end of the current iteration of the run loop, the object will get released either way.
The biggest benefit of using -autorelease is that you don't have to worry about the lifetime of the object in the context of your method. So, if you decide later that you want to do something with an object several lines after it was last used, you don't need to worry about moving your call to -release.
The main instance when using -release will make a noticeable difference vs. using -autorelease is if you're creating a lot of temporary objects in your method. For example, consider the following method:
- (void)someMethod {
NSUInteger i = 0;
while (i < 100000) {
id tempObject = [[[SomeClass alloc] init] autorelease];
// Do something with tempObject
i++;
}
}
By the time this method ends, you've got 100,000 objects sitting in the autorelease pool waiting to be released. Depending on the class of tempObject, this may or may not be a major problem on the desktop, but it most certainly would be on the memory-constrained iPhone. Thus, you should really use -release over -autorelease if you're allocating many temporary objects. But, for many/most uses, you wont see any major differences between the two.
I agree with Matt Ball. Let me just add that, if you find yourself using this pattern frequently, it can be handy to write a quick category:
#interface UIView (MyCategories)
- (UIView *)addNewSubviewOfType:(Class)viewType inFrame:(NSRect)frame;
#end
#implementation UIView (MyCategories)
- (UIView *)addNewSubviewOfType:(Class)viewType inFrame:(NSRect)frame
{
UIView * newView = [[viewType alloc] initWithFrame:frame];
[self addSubView:newView];
return [newView autorelease];
}
#end
Which can be used as follows:
UIView * view = [someView addNewSubviewOfType:[UIView class]
inFrame:someFrame];
And it even works with other types, as long as they are derived from UIView:
UIButton * button = [mainView addNewSubviewOfType:[UIButton class]
inFrame:buttonFrame];
I usually go for -release rather than -autorelease whenever possible. This comes from years of experience debugging and enhancing other people's Objective-C code. Code that uses autorelease everywhere makes it harder to debug when an object gets over-released, since the extra release happens far away from the incorrect code.
It's also the case that many folks use autorelease when they just don't understand how cocoa memory management works. Learn the rules, learn the API, and you'll almost never need to autorelease an object.
A last minor point is that if you don't need the autorelease behavior, then using autorelease just needlessly adds extra work for your program to do.

I need help with Animation Callbacks (iPhone)

I am creating an application in iPhone and I have several UIViews and layers in it. I am doing some animations using CAKeyframeAnimation class and since the animations have to be chained, I have overridden the animationDidStop method in UIView.
I am getting the callbacks properly, however I just couldn't figure out how I can find which animation was ended so that I can start the next one. Only parameters to the callback function is a CAAnimation object and a boolean.
I can workaround this problem by setting a property in the class and using an enum for the various animations I use. However I just wanted to know if there is any built in attributes in the callbacks which I can set in the CAKeyframeAnimation object and then refer the same in the callback.
Any help would be greatly appreciated!
You can specify a name for an animation and read it in your delegate method.
[animation setValue:"firstAnimation" forKey:#"name"];
...
- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished {
if([[animation valueForKey:#"name"] isEqual:#"firstAnimation"] && finished) {
...
}
}
I know that you said that you're using CAKeyframeAnimations, but if you want simple animation of UIView properties (origin, bounds, alpha, etc.), you can wrap the change of the property or properties in a begin / commit block and specify a delegate method that is called upon completion of the animation. As long as the delegate method takes three arguments, you can call it whatever you want. For example:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:ANIMATIONDURATIONINSECONDS];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(yourAnimationHasFinished:finished:context:)];
// Change property or properties here
[UIView commitAnimations];
will cause the method
- (void)yourAnimationHasFinished:(NSString *)animationID finished:(BOOL)finished context:(void *)context;
to be called. The arbitrary naming this allows would provide you with a means of separating handling for the completion of different animations. I prefer this for simple animations.
For dealing with more complex animations that interact directly with CALayers, the animationDidStop:finished: delegate method does return the animation object that has finished. If you are making one instance that is the delegate for multiple animations, you could create an NSMutableDictionary of animations and NSNumbers for use in a switch statement within the animationDidStop:finished: method. As you create the CAKeyframeAnimation, use setObject:forKey: to assign it to its matching number, then use objectForKey: to find the number that corresponds to that animation in the completion method and feed that into a switch statement.