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.
Related
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.
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];
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)
I can't figure this one out, hopefully someone can help.
This is the code in my ViewController.h file.
{
NSArray *bgImages;
int currentBackground;
bool hasShrunk;
bool hasMoved;
CGAffineTransform translate;
CGAffineTransform sizeShrink;
CGAffineTransform sizeGrow;
}
Here is some of my code in the ViewController.m file.
- (void)viewDidLoad {
hasMoved = NO;
hasShrunk = NO;
currentBackground = 0;
bgImages = [[NSArray alloc] initWithObjects:
[UIImage imageNamed:#"WallPaper_01.png"],
[UIImage imageNamed:#"WallPaper_02.png"],
[UIImage imageNamed:#"WallPaper_03.png"],
[UIImage imageNamed:#"WallPaper_04.png"],
[UIImage imageNamed:#"WallPaper_05.png"],
nil];
sizeShrink = CGAffineTransformMakeScale(.25,.25);
sizeGrow = CGAffineTransformMakeScale(1,1);
_myBackground.image = [bgImages objectAtIndex:currentBackground];
}
Here is the issue. This code and the entire project run fine on the simulator, no issues. When I change the output to an actual iPhone device it fails. The code is failing on the allocation of the array called bgImages. If I place a breakpoint on viewDidLoad and step into it line by line, it executes up to the array allocation line and then jumps over it. When it then attempts to execute the line:
_myBackground.image = [bgImages objectAtIndex:currentBackground];
it will fail because the array is empty. I don't understand how this can work in the simulator but not on the actual device. The code does not look incorrect but I am at my wits end trying to understand why it behaves this way. Have I coded it incorrectly?
You're getting a problem because the array doesn't have any elements in it. One possibility is that all of the [UIImage imageNamed:...] calls are returning nil. Break one of these out as a separate variable, e.g.
UIImage *firstImage = [UIImage imageNamed:#"WallPaper_01.png"];
and set a breakpoint on that line to make sure that the image is being initialized correctly. If it isn't, one possible reason is that you have the 2x version (WallPaper_01#2x.png) but not the 1x version, or vice versa.
Edit: As #rdelmar notes, another possible reason is that, since Mac OS X uses (by default) a case-insensitive filesystem but iOS uses a case-sensitive filesystem, you're using the wrong filename, but you're only seeing a problem on iOS because OS X doesn't care that the filename isn't exactly right.
You seem to be loading all images, then just using one. This may lead to memory problems in a real device. What you may want to do is something like this;
bgImages = [[NSArray alloc] initWithObjects:
[#"WallPaper_01.png"],
[#"WallPaper_02.png"],
[#"WallPaper_03.png"],
[#"WallPaper_04.png"],
[#"WallPaper_05.png"],
nil];
...
_myBackground.image =
[UIImage imageNamed:[bgImages objectAtIndex:currentBackground]];
Also, have a look at #rdelmar's comment about case sensitivity.
I'm trying to store UIImage datas in NSArray, actually 60 images, each with size of 300kb. Then, I'm trying to animate that images in UIImageView.
My code:
NSMutableArray *arr = [[NSMutableArray alloc] init];
for (int i=0; i<61; ++i) {
[arr addObject:[UIImage imageNamed:[NSString stringWithFormat:#"%i.png", i]]];
}
img.animationImages = arr;
img.animationDuration = 1.5;
img.contentMode = UIViewContentModeBottomLeft;
[img startAnimating];
When I test it in iPad Simulator 4.3, it works fine.
When I want to test it on my device (iPad 1), application crashes.
Note: App does not crashes if I comment this code: [img startAnimating];
1. What could be the problem? I think it is memory problem...?!
2. Can I store a loooots of UIImages in NSArray?
Your png files may have 300KB, but this is a compressing format.
To get an idea of the size of the Image itself you should multiply the width, height and bytes per pixel.
i.e. If an image has the size 1024 * 1024 and RGBA model, the images itself has an size of 4MB in the memory. And this is just one image. If you have 300 it is about 120MB.
Note: this is only a rough rule of thumb, but it gives you an idea.
So you should keep path-names in the array and load the image only if need, and thumbnails should be resized and stored on the disk as files. Do not just scale the UIImageView.
Here's a great article with code about resizing.
I solved the app crash problem with alternative way: I tried to use simple NSTimer. For each iteration I set n'th image to UIImageView:
I have UIImageView in xib file, pointing to img variable.
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:.03
target:self
selector:#selector(tiktak)
userInfo:nil repeats:YES];
int n = 0;
-(void)tiktak {
n++;
img.image = [UIImage imageNamed:
[NSString stringWithFormat:#"Blue Menu_%i.png", n]];
if(nn == 60) nn = 0;
}
Animation is amazing. I've also tested it in Activity Monitor, CPU usage is 6% and less.
I would not advise you store that many UIImages in an NSArray, its way too heavy and inefficient. From where are these images from (bundle, internet, device's gallery)?
Edit: I don't know how you want it to look, but you can build the array with the names of the UIImages, and then load the UIImage and show it. Like this:
for(i=0;i<60;i++){
NSString *nameOfPicture=[NSString stringWithFormat:#"picture_%d",i+1]
[array addObject:nameOfPicture];
}