CAKeyframeAnimation - Animating an Array of Images creates a huge allocation after completion - ios

I'm trying to animate an array of UIImage with CAKeyframeAnimation. Easy in theory.
Sample code at the bottom of the post.
My problem is that after the animation did finish, I've got a huge leak that is impossible to get rid of it.
Code to init CAKeyframeAnimation:
- (void)animateImages
{
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
keyframeAnimation.values = self.imagesArray; // array with images
keyframeAnimation.repeatCount = 1.0f;
keyframeAnimation.duration = 5.0;
keyframeAnimation.removedOnCompletion = YES;
CALayer *layer = self.animationImageView.layer;
[layer addAnimation:keyframeAnimation
forKey:#"flingAnimation"];
}
Adding a delegate to the animation and removing the animation manually cause the same leak effect:
... // Code to change
keyframeAnimation.delegate = self;
// keyframeAnimation.removedOnCompletion = YES;
keyframeAnimation.removedOnCompletion = NO;
keyframeAnimation.fillMode = kCAFillModeForwards;
....
Then:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
if (flag)
{
[self.animationImageView.layer removeAllAnimations];
[self.animationImageView.layer removeAnimationForKey:#"flingAnimation"]; // just in case
}
}
The result is always a huge allocation. The size of the stack of memory is proportional to the size of the images:
I uploaded an example to GitHub to check the code.

SOLVED
I found the problem.
As gabbler was saying there was not a leak problem. The problem was a high allocation of Images.
I was releasing the array with the images, however, the images did not disappear from memory.
So finally I found the problem:
[UIImage imageNamed:#""];
From method definition:
This method looks in the system caches for an image object with the specified name and returns that object if it exists. If a matching image object is not already in the cache, this method locates and loads the image data from disk or asset catelog, and then returns the resulting object. You can not assume that this method is thread safe.
So, imageNamed: stores the image in a private Cache.
- The first problem is that you can not take control of the cache size.
- The second problem is that the cache did not get cleaned in time and if you are allocating a lot of images with imageNamed:, your app, probably, will crash.
SOLUTION:
Allocate images directly from Bundle:
NSString *imageName = [NSString stringWithFormat:#"imageName.png"];
NSString *path = [[NSBundle mainBundle] pathForResource:imageName
// Allocating images with imageWithContentsOfFile makes images to do not cache.
UIImage *image = [UIImage imageWithContentsOfFile:path];
Small problem:
Images in Images.xcassets get never allocated. So, move your images outside Images.xcassets to allocate directly from Bundle.
Example project with solution here.

Related

Is there any alternate for animating array of images using UIImageView?

UIImageView has animationImages for animating sequence of images. That works fine. But It holds the images object. So There is a spike in use of memory when this animation is happening. I tried NSTimer for setting image property of that image view, But it doesn't work.
Can we achieve this in any other approach?
Instead of looking for alternatives, just try to correct the existing code.
The best practice of allocating an image for animationImages should be using initWithContentsOfFile instead of imageNamed:
imageNamed: it cache’s your images and you lose control over the memory - there's no guarantee that releasing the object will actually release the image but does provide faster loading of images second time around as they are cached.
imageWithContentsOfFile: it does not cache images and is more memory friendly however as it does not cache images and the heavier images are loaded much slower.
When the animation does stop, just release the image collection array. If you are using ARC then make it the image collection array to nil.
Best Practice:
for(int itemIndex = 0; itemIndex < 20; itemIndex++) {
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"myImage1" ofType:#"png"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath];
[imageArray addObject:image];
}
After the Animation:
imageArray = nil;

Memory leaks when loading images to scroller

I have a scrollView in which i load images into from the net .I sometimes get memory warnings, which i assume are because i am doing something wrong with the images loader.
I am trying to fix little things, and i just wanted to show the code here, and hear maybe there are more things i can fix to get rid of this warnings.
So every time the scroller (iPad) has only 4/5 images that are : current page-3->current page+3.
This is how i load the images(every image has also a blur effect with Apple's classes) :
(should i allocated imageView every time? can i improve something here? )
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^
{
NSData *imdata2 = [NSData dataWithContentsOfURL:url];
dispatch_async(dispatch_get_main_queue(), ^
{
UIImage *theImage=[UIImage imageWithData:imdata2 scale:1];
UIImage *LightImage = [theImage applyLightEffect];
UIImage *scaledImage =[resizer resizeImageToWidth:[Globals sharedGlobals].imagesWidth WithImage:theImage];
CGRect viewSizeBack=CGRectMake(scroller.bounds.size.width*toPage , 0, scroller.bounds.size.width, scroller.bounds.size.height);
int x=[Globals sharedGlobals].pageMargins;
int y=([UIScreen mainScreen].bounds.size.height-scaledImage.size.height)/2;
CGRect viewSizeFront=CGRectMake(x , y, scaledImage.size.width,scaledImage.size.height);
UIImageView *backImageView=[[UIImageView alloc] initWithFrame:viewSizeBack];
UIImageView *frontImageView=[[UIImageView alloc] initWithFrame:viewSizeFront];
backImageView.layer.cornerRadius = 0.0;
backImageView.layer.masksToBounds = YES;
backImageView.image=LightImage;
frontImageView.layer.cornerRadius = 0.0;
frontImageView.layer.masksToBounds = YES;
frontImageView.image=scaledImage;
frontImageView.layer.borderWidth=1.0;
frontImageView.layer.borderColor=[UIColor colorWithRed:255.0 green:255.0 blue:255.0 alpha:1.0].CGColor;
[backImageView addSubview:frontImageView];
backImageView.tag=toPage;
frontImageView.tag=toPage;
[scroller addSubview:backImageView];
});
});
You should only ever have 3 images loaded at a maximum - the previous page (if it exists), the current page and the next page.
Any other images you have loaded above this is wasteful because you can't see them and they're just taking up memory for no good reason. If the images aren't too big then you can maintain them in memory and purge them when you get a warning, but for large images this will still generally cause you issues.
If you don't use ARC then add this:
[backImageView autorelease];
[frontImageView autorelease];

UIImageView+animatedGIF always LOOPS

I used class made by "mayoff" (Rob Mayoff) "UIImageView+animatedGIF" which was proposed in one of the answers here on stackoverflow. UIImageView+animatedGIF
With it I can import animated .gif images in UIImageView on iOS. This class works flawlessly, but the only problem is that .gif is always looping. No matter how I export it (I am exporting image from photoshop - 67 frames, set repeat to "once") it loops forever in UIImageView.
I am importing my .gif with these two lines:
NSURL *url = [[NSBundle mainBundle] URLForResource:#"Loading" withExtension:#"gif"];
self.loadingImageView.image = [UIImage animatedImageWithAnimatedGIFData:[NSData dataWithContentsOfURL:url]];
very simple and it works.
I have tried setting animationRepeatCount property to 1, as recomended by Rob but that didn't do the trick. Also I have tried setting the UIImageView's animationImages property to
gifImage.images too, instead of just setting the view's image property
to gifImage. In that case, .gif is not animating at all.
Any ideas how to play that .gif only once? I can think of many tricks (like setting last frame of the gif to show up after some time but I'd first try something simpler if possible).
I took the test app from that project and changed the viewDidLoad method to this:
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *url = [[NSBundle mainBundle] URLForResource:#"test" withExtension:#"gif"];
UIImage *testImage = [UIImage animatedImageWithAnimatedGIFData:[NSData dataWithContentsOfURL:url]];
self.dataImageView.animationImages = testImage.images;
self.dataImageView.animationDuration = testImage.duration;
self.dataImageView.animationRepeatCount = 1;
self.dataImageView.image = testImage.images.lastObject;
[self.dataImageView startAnimating];
}
When the app launches, it animates the image once and then shows just the last frame. If I connect a button that sends [self.dataImageView startAnimating], then tapping the button runs the animation one more time, then stops again on the last frame.
Make sure you're setting your image view up the way I do above. Make sure you are not setting the image view's image property to the animated image. The image view's image property must be set to a non-animated image if you want the animation to stop.
I have forked "uiimage-from-animated-gif" made by "Dreddik" and added a delegate method
#pragma mark - AnimatedGifDelegate
- (void)animationWillRepeat:(AnimatedGif *)animatedGif
{
NSLog(#"animationWillRepeat");
//[animatedGif stop];
}
So you know the time when animation will repeat and do your own thing in middle of every repetition. You may count the number of times animation repeated and stop animation at a certain number reached.
The forked repository is at https://github.com/rishi420/Animated-GIF-iPhone
Was having issues in start/stp animation & managing animation count
So used this pod
imageViewObject.animate(withGIFNamed: "gif_name", loopCount: 1) {
print("animating image")
}
To start/stop animation :
imageViewObject.isAnimatingGIF ? imageViewObject.stopAnimatingGIF() : imageViewObject.startAnimatingGIF()

Apparent leaks: png_malloc

I have an application with various animations and images. The application runs just fine for about 30 minutes, but then crashes. I have looked through the instruments and I notice that there are a whole bunch of 7kB png_malloc allocations building each time I mark the heap (amounting to about 300kB every couple minutes).
I noticed in my leaks that every time an animation or png is used for the first time, there seems to be a "leak" of the data (although I am a bit skeptical whether this is a real leak or not).
All of these images have been declared using
frameName = [[NSString alloc] initWithFormat:#"image.png"];
UIImage * u = [UIImage cachelessImageNamed:frameName];
so I don't believe there should be a problem with caching the images.
Has anyone else had the same problem with this png_malloc allocation?
The instruments screenshot
*Notes: I am using arc and the animations are getting set to nil in the deallocation function; however, these isn't called until the application exits. Does this create a problem each time the animation is run if it's only been created once?
EDIT Some more code:
-(void) createSymbolAnimations
{
if (symbolAnimations == nil)
{
symbolAnimations = [[NSMutableArray alloc]init];
}
NSString * frameName;
if (thisAnimation == nil)
{
thisAnimation = [[NSMutableArray alloc] init];
}
for (int x= 0; x< 40; x++)
{
frameName = [[NSString alloc] initWithFormat:#"image%d%s",x,".png"];
UIImage * u = [UIImage cachelessImageNamed:frameName];
[thisAnimation addObject:u];
}
[symbolAnimations addObject:thisAnimation];
}
Is the creation of the animation. Imagine I have a few of these and then I change the animation set and start animating on touch with this snippet:
UIImageView * aView = [frameArray objectAtIndex:x];
aView.image = [[symbolAnimations objectAtIndex:x]objectAtIndex:0];
[aView startAnimating];
Where x is the set of images I want to animate and 0 is the first frame of the animation.
So the image is changed quite a few times and I'm starting to worry that each time the animation images are changed, the RAM isn't cleared but instead over/rewritten.
EDIT Image grabber
+(UIImage *) cachelessImageNamed: (NSString *) name
{
return [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:name ofType:nil]];
}
Just in case anyone stumbles upon this later, I found the problem.
The pngs used in this project for animations were created in Windows (not sure how pertinent that is) and it seems the file format is slightly different than the png that XCode is expecting. This disallows any png from being deallocated. If you convert the format to a png for Mac, it seems to work fine. I did this through
mogrify -type truecolormatte -format png *.png
After adjusting all of my images, the leaks were greatly reduced and everything seems to run fine.

Should retain count increase after an image rotation?

I'm using the following code to rotate an image
http://www.platinumball.net/blog/2010/01/31/iphone-uiimage-rotation-and-scaling/
that's one of the few image transformations that I do before uploading an image to the server, I also have some other transformations: normalize, crop, resize.
Each one of the transformations returns an (UIImage*) and I add those functions using a category. I use it like this:
UIImage *img = //image from camera;
img = [[[img normalize] rotate] scale] resize];
[upload img];
After selecting 3~4 photos from the camera and executing the same code each time I get a Memory Warning message in XCode.
I'm guessing I have a memory leak somewhere (even though im using ARC). I'm not very experienced using the xCode debugging tools, so I started printing the retain count after each method.
UIImage *img = //image from camera;
img = [img normalize];
img = [img rotate]; // retain count increases :(
img = [img scale];
img = [img resize];
The only operation that increases the retain count is the rotation. Is this normal?
The only operation that increases the retain count is the rotation. Is this normal?
It's quite possible that the UIGraphicsGetImageFromCurrentImageContext() call in your rotate function ends up retaining the image. If so, it almost certainly also autoreleases the image in keeping with the normal Cocoa memory management rules. Either way, you shouldn't worry about it. As long as your rotate function doesn't itself contain any unbalanced retain (or alloc, new, or copy) calls, you should expect to be free of leaks. If you do suspect a leak, it's better to track it down with Instruments than by watching retainCount yourself.

Resources