Animation Terminating due to Memory even with ARC and imageFilePath - ios

I'm doing a simple animation of png's after my app loads and displays its launch screen. The animation works in the simulator but not on the actual iPhone, where it terminates due to memory pressure (the launch screen does load first though). In the debug, the Memory increases exponentially to like 200MB until it crashes. The instruments show no leaks, All Heap and anonymous Vm 9.61 MB overall. The animation doesn't show at all on the actual iPhone.
I've already stopped using imageNamed to set the animation images and used imageFilePath as suggested by another help topic. It works and the images load on the simulator. I'm just not sure what else to do. Help would be very much appreciated.
In didFinishLaunchingWithOptions:
[self.window makeKeyAndVisible];
self.animationView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0, 320, 480)];
NSArray *animationArray = [[NSArray alloc] initWithObjects:
[UIImage imageFromMainBundleFile:#"/Splash_00000.png"],
[UIImage imageFromMainBundleFile:#"/Splash_00001.png"],
//There's about 150 of these so I'll omit the rest
nil];
self.animationView.animationImages = animationArray;
self.animationView.animationDuration = 5.0f;
self.animationView.animationRepeatCount = 1;
[self.animationView startAnimating];
[self.window addSubview:self.animationView];
[self.window bringSubviewToFront:self.animationView];
In case it's needed, this is the method I'm using that I got from another thread:
+(UIImage*)imageFromMainBundleFile:(NSString*)aFileName
{
NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
return [UIImage imageWithContentsOfFile:[NSString stringWithFormat:#"%#/%#", bundlePath, aFileName]];
}

Putting aside that splash screens aren't recommended, you're not leaking but you're running out of heap (which is why it works on the simulator).
Each of those images is owned by the array and won't be released by ARC until long after the animation has completed. Bare in mind that the PNGs, while compressed on disk, will be uncompressed in memory.
Solutions - there are a couple that spring to mind.
Split the animations into a sequence of discrete phases and ensure
that images are released (using #autoreleasepool) between each phase
More realistically, render the animation as a movie and play that
instead (far less likely to stutter between phases)

Related

UITabBarItem memory leak

I have an issue with this code in iOS9, this codes causes a memory leak each
time it is called. I found this leak in instruments and the iOS function that seems to be leaking is [UITabBarButton initWithImage:selectedImage:label:withInsets:].
UITabBarItem *item0 = [tabBarLibrary.items objectAtIndex:0];
item0.image = [UIImage imageNamed:#"TabBarIcon1.png"];
Anyone else have this issue or have a way to work around it? Basically the code is switching the icon for the tab bar depending on the situation nothing complicated.
I set any existing image to nil before setting the new one.
UITabBarItem *item0 = [tabBarLibrary.items objectAtIndex:0];
item0.image = nil;
item0.image = [UIImage imageNamed:#"TabBarIcon1.png"];
This enables ARC to free up memory used by any existing image.

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

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.

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];

ImageCaching on IOS

I've some trouble with memory on my app. I've checked Instruments to get more clue about this issue and i've found that 79% of my memory is used by this :
So i've searched on Google and some people said that is image caching which saved in memory all my images. Maybe it comes from my allocation ?
Here is how i call my images :
info = [InfoModel getInfo:[NSString stringWithFormat:#"%d", self.idEnigme]];
myImage = [UIImage imageNamed:[NSString stringWithFormat:#"res/img/%#", [info objectForKey:#"path1"]]];
myImageView = [[UIImageView alloc] initWithImage:myImage];
myImageView.frame = CGRectMake(0, 200, [[UIScreen mainScreen] bounds].size.width, 400);
[self.scrollView addSubview:myImageView];
Info is a class where i parse a Json file where are my path to images.
Thanks for helping, this drives me crazy.
iOS automatically caches your image for future use when you call imageNamed:
As discussed in a few places, including here:Does UIImageView cache images?
You can get around this caching if you know you are only going to create it once by using
[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:fileName ofType:nil]]
instead
I wouldn't worry about that too much, the UIImage cache is cleared when the app receives a low memory warning. It's all handled automatically, so any images that are no longer in use will be flushed from memory at this point.
So if your app is crashing from running out of memory it is not likely because the OS is caching images that are no longer in use.
You can handle your own caching of images by using initWithData instead of imageNamed but I doubt this solution will help you.
I'm very worried about the following screen. When i launch "Instruments" in Allocations mode, i see every image of my app adding to "Living". I really don't understand how it could be possible...
I've found the problem and i solved it. I'll explain you what was the problem, maybe it could help someone.
The real problem was my UIImage init. Initially i allocated my UIImage in method like the following code :
NSString* fileName = [NSString stringWithFormat:#"%#%#%#", name, number, ext];
UIImage *myImageHeader = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:fileName ofType:nil]];
UIImageView *myImageViewHeader = [[UIImageView alloc] initWithImage:myImageHeader];
myImageViewHeader.frame = CGRectMake(0, 0, [[UIScreen mainScreen] bounds].size.width, 277);
[self.scrollView addSubview:myImageViewHeader];
Finally i've decided to create a UIImageview in my .h via a property and to set it in my method, like this :
#.h
#property (weak, nonatomic) IBOutlet UIImageView *headerImage;
#.m
[self.headerImage setImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:fileName ofType:nil inDirectory:nil]]];
By this way i saved a lot of memory but i still have leaks. So i decided to delete the image in my UIImageView after use :
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^(void){
self.headerImage.image = nil;
});
Now, my app only use 8mb of memory and manage memory correctly.
Thx for helping !

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.

Resources