Memory Warning & Crash ImageIO-jpeg-data - ios

I have created an app and it is (well it was) ready to submit.
The app is basically a flash cards apps for kids where there are 12 cards per page.
It is currently iPad only and when the cards are touched a image view animates out of the card to full screen with a full resolution 2048 x 1536 and it plays a sound of the animal, you tap the full size image and it goes back in the card. Each card has 5 images assigned to it. The app has been working perfectly all though the design period and now that I have loaded all of the animals and I have been testing it and noticed it crashed.
Long story short the memory continually grows by 12MB per image (although each image is around 200-400KB) until it reaches 700MB of Memory and it crashes. This memory never reduces.
I have run the app through the xcode instruments and can see no leaks but have identified in the 'Allocations' that it is the 'ImageIO-jpeg-data' category that is the culprit.
I have spent all evening reading up about this as I have found some promising results however these results have not worked for me so I am assuming that I have misunderstood what has been advised or I have not understood so was hoping that someone could maybe help me out or explain in laymen terms how I can resolve this.
OK so to explain how I have the view contoller set up.
Originally I set up the 5 images for each card in an array but there was a delay so someone on here recommended that I pre load the images in viewDidLoad and then call them when the card/button was touched.
So for example I was using this format in viewDidLoad
domestic100 = [UIImage imageNamed:#"ferret-00"];
domestic101 = [UIImage imageNamed:#"ferret-01"];
domestic102 = [UIImage imageNamed:#"ferret-02"];
domestic103 = [UIImage imageNamed:#"ferret-03"];
domestic104 = [UIImage imageNamed:#"ferret-04"];
and then in the the button pressed I assigned the image to the image view by calling:
domestic01ImageContainer.image = domestic100;
inside a if else look checking if the image number was 1 to 4.
Anyway it worked perfectly (apart form this memeory issue)
I have read online that I would be better off declaring the uiimages using
[UIImage imageWithContentsOfFile:path]
as seen in this post:
UIImage memory not deallocating VM: ImageIO_JPEG_DATA?
So the alterations I have made in my code are:
in my viewDidLoad I now create uiimages as so:
domestic200 = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource: #"dog-00" ofType: #"jpg"]];
domestic201 = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource: #"dog-01" ofType: #"jpg"]];
domestic202 = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource: #"dog-02" ofType: #"jpg"]];
domestic203 = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource: #"dog-03" ofType: #"jpg"]];
domestic204 = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource: #"dog-04" ofType: #"jpg"]];
and then in the button pressed call the image in to the imageview using
[domestic01ImageContainer setImage:domestic100];
I am getting the same results - the image are just building up in the same memory and not releasing or deallocating.
I'm no wiz when it comes of objective-c and this is my first real app.
If someone could be kind enough to spare a moment of their time to help me I would be really appreciative.
Thanks in advance.
****** Edit ******
So I have told you how I load my images above, I also in the viewDidLoad give the 12 imageviews a starting point and a hidden alpha.
domestic01ImageContainer = [[UIImageView alloc] initWithFrame:CGRectMake(30, 123, 152, 224)];
domestic01ImageContainer.Alpha = 0;
I then add the subview with:
[self.view addSubview:domestic01ImageContainer];
each imageview is allocated a tap gesture to hide the image at a later point:
UITapGestureRecognizer *domestic01Tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imageTaped:)];
[domestic01ImageContainer addGestureRecognizer:domestic01Tap];
[domestic01ImageContainer setUserInteractionEnabled:YES];
When the thumb / card is pressed a streamlined version of the button would be:
- (IBAction)domestic01ButtonClicked:(id)sender {
static int imageNumber = 0;
if (imageNumber == 0) {
[domestic01ImageContainer setImage:domestic100];
imageNumber++;
}
else if (imageNumber == 1) {
[domestic01ImageContainer setImage:domestic101];
imageNumber++;
}
else if (imageNumber == 2) {
[domestic01ImageContainer setImage:domestic102];
imageNumber++;
}
else if (imageNumber == 3) {
[domestic01ImageContainer setImage:domestic103];
imageNumber++;
}
else if (imageNumber == 4) {
[domestic01ImageContainer setImage:domestic104];
imageNumber = 0;
}
domestic01ImageContainer.Alpha = 1;
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
domestic01ImageContainer.frame = CGRectMake(0, 0, 768, 1024);
} completion:^(BOOL finished) {
}];
}
The in my imageTapped recognizer to shrink the full size image back to the card I use:
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
domestic01ImageContainer.frame = CGRectMake(30, 123, 152, 224);
} completion:^(BOOL finished) {
domestic01ImageContainer.Alpha = 0;
}];
Which basically put the flash card back from full screen to the size of the card / thumbnail and the sets alpha again to 0.
Like I said it all works but as you have suggested the image is being retained somewhere.
Hope this sheds a bit more light on what could be causing the issue.
I had tried setting the image to nil in after the alpha was set to 0 in the imageTapped but this didn't clear the memory.
[domestic05ImageContainer setImage:nil];

Ok so if anyone else is suffering from this issue then here is the solution that I found that work perfectly.
http://xcodenoobies.blogspot.co.uk/2011/02/best-way-to-use-uiimageview-memory.html
Thanks Anna for your input, it pointed me in the right direct but his was the answer that did it for me.

I was having a similar issue. Memory was being held on abnormally. I had a UIImageView IBOutlet and I was setting its image property with my view controller's UIImage property, which I would set while initializing my view controller. The UIImageView would hold on to the UIImage property of my view controller wrongly even when I would go back to the root view controller.
I fixed the issue by changing the view controller's UIImage property to NSData and setting the UIImageView's image property as follows:
[_previewImageView setImage:[UIImage imageWithData:_previewImageData]];
This resolved the issue of ImageIO-jpeg-data being help on unnecessarily. Hope this helps someone.

Related

How to add animate images and text in the same view programatically?

I am currently learning iOS development and I want to develop an iOS app where there is an animated image at the beginning top of the View Controller and a lot of text just after it. Please have a look at the picture below:
I have thought about the codes for both the image animation at the top and the large amount of text just after it. I was wondering whether someone could please review them and tell me whether they are fine or not.
The codes for both the image animation and the large amount of text will be placed inside the viewDidLoad method of the implementation file of the controller like this:
- (void)viewDidLoad
{
[super viewDidLoad];
// codes for image animation
// codes for large amount of text to be displayed
}
IMAGE ANIMATION CODES
For the image animation, I am going to use 5 png pictures: A.png, B.png, C.png, D.png and E.png. The slide show will start from A, then move on to B, then to C, to D and finally to E. All the 5 pictures are added to my project in Xcode. Below are the codes:
NSArray *imagelist = [[NSArray alloc] initWithObjects: [UIImage imageNamed:#"A.png"], [UIImage imageNamed:#"B.png"], [UIImage imageNamed:#"C.png"], [UIImage imageNamed:#"D.png"], [UIImage imageNamed:#"E.png"],nil];
UIImageView *animationImageView = [[UIImageView alloc] initWithFrame: CGRectMake(5,5,90,180)];
animationImageView.animationImages = imagelist;
animationimageView.animationDuration = 10;
[self.view addSubView:animationimageView];
[animationImageView startAnimating];
LARGE AMOUNT OF TEXT CODES
For the text, I have not added all the necessary text....because there is a lot more. That is why I need the text object to be scrollable. Below are the codes:
UITextView *largeText = [[UITextView alloc] initWitthFrame: CGRectMake(5,100,90,180)];
largeText.text = #"In organizations, we must work with and for others. To be able to mutually achieve our goals, we must be able to relate to others effectively. These leadership tips will help you do just that:
1)Catch people doing things right and then let them know that they are doing things right
2)Use feedback to stay informed about what other people are doing in your area of responsibility and authority
3)Have regular, focused meetings regarding the projects that you are responsible for
4)Provide adequate instructions. Time is lost if things are not done correctly
5)Train others to do jobs. You cannot do them all, nor can others do them if they have not been trained
6)Expect others to succeed. It becomes a self-fulfilling prophecy when you believe others are loyal, dedicated and doing a good job";
largeText.font = [UIFont fontWithName:#"TimesNewRomanPSMT" size:18];
largeText.editable = NO;
largeText.scrollEnabled = YES;
[self.view addSubView:largeText];
Please review the codes and can someone tell me whether the codes are fine or not.
Thanks for reading.
NSArray *imagelist = [[NSArray alloc] initWithObjects: [UIImage imageNamed:#"A.png"], [UIImage imageNamed:#"B.png"], [UIImage imageNamed:#"C.png"], [UIImage imageNamed:#"D.png"], [UIImage imageNamed:#"E.png"],nil];
UIImageView *animationImageView = [[UIImageView alloc] initWithFrame: CGRectMake(5,5,90,180)];
[self.view addSubView:animationimageView];
NSTimeInterval delay=0.0;
for (NSInteger i=0; i<[imagelist count]; i++) {
delay+=3.0;
[UIView animateWithDuration:0.5 delay:delay options:UIViewAnimationOptionCurveLinear animations:^{
animationImageView.image=[imagelist objectAtIndex:i];
}completion: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];

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 !

What limitations of an iPad 4 should I take into account when working with an array of images to produce an animation?

Here is what I'm trying to do: I want to have an app that displays a full-screen image and have that image animate by sliding a UISlider. A changing slider value will sequence through images of an array creating an animation. This animation is a single character on a turn-table. The character looks as though he is rotating around the turn table as the slider changes value.
This project is a portfolio piece for a 3D artist. The artist gave me a sequence of 180 images of the character rendered at the full screen retina resolution. He also gave be an additional 180 images rendered at the non-retina full screen resolution. The idea is that when somebody is viewing his character from any angle on a retina iPad, they can toggle using a UISwitch between retina and non-retina display. The code posted below works fine on the simulator.
However when running this code on an iPad 4, it works for a bit, then I get a memory warning. Then it crashes. I'm assuming having that many images of that size being displayed as fast as somebody wants to move a slider is too much for the iPad 4 to handle.
I'm curious what limitations I should take into account when working with images like this. Is 180 images way too many? What is a reasonable amount? Is there a more efficient way of producing my desired result? Instead of resorting the guess-and-check method of how many images would not cause it to crash, I figured somebody might have some useful insight on my issue.
#implementation RBViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_imageDisplay.image = [UIImage imageNamed:#"HyperReal_0.png"];
_arrayRetina = [[NSMutableArray alloc] initWithCapacity:180];
_arrayNormal = [[NSMutableArray alloc] initWithCapacity:180];
for(int i = 0; i < 179; i++)
{
NSString *myImageString = [[NSString alloc] initWithFormat:#"HyperReal_%i.png", i];
UIImage *myImageGraphic = [UIImage imageNamed:myImageString];
[_arrayRetina addObject:myImageGraphic];
}
for(int i = 0; i < 179; i++)
{
NSString *myImageString = [[NSString alloc] initWithFormat:#"lameHyperReal_%i.png", i];
UIImage *myImageGraphic = [UIImage imageNamed:myImageString];
[_arrayNormal addObject:myImageGraphic];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)switchButton:(id)sender {
if (_switchOnForRetina.isOn == YES)
{
_imageDisplay.image = [_arrayRetina objectAtIndex:_sliderBar.value];
}
else
_imageDisplay.image = [_arrayNormal objectAtIndex:_sliderBar.value];
}
- (IBAction)changeSlider:(id)sender {
if (_switchOnForRetina.isOn == YES)
{
_imageDisplay.image = [_arrayRetina objectAtIndex:_sliderBar.value];
}
else
_imageDisplay.image = [_arrayNormal objectAtIndex:_sliderBar.value];
}
#end
Instead of loading all 180 images at once into an array, couldn't you instantiate the current image to be displayed during the slider's continuous UIContolEventValueChanged event?
Create the slider
UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(0, 0, 155, 20)];
slider.continuous = YES;
slider.value = 0.0f;
slider.minimumValue = 0.0f;
slider.minimumValue = 179.0f;
[slider addTarget:self action:#selector(handleContinuousSlider:) forControlEvents:UIControlEventValueChanged];
Handle it's continuous change event
- (void)handleContinuousSlider:(UISlider *)slider {
//create a UIImage with file name that makes the current integer value of the slider
NSString *myImageString = [[NSString alloc] initWithFormat:#"HyperReal_%i.png", slider.value];
UIImage *myImageGraphic = [UIImage imageNamed:myImageString];
//I assume this is a UIImageView
[_imageDisplay setImage:myImageGraphic];
[myImageGraphic release];
}
Prebuffering memory cost is roughtly:
((2048*1536*4 + 1024*768*4)*180)/1024/1024/1024 = 2.63671875 giga bytes
Even if there some underground optimisation going on, your iPad dont have enough memory to safely run that program.
As a work around your crashes, dont preload all images and instead just load required one
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[ self changeSlider: nil ];
}
- (IBAction)changeSlider:(id)sender {
if (_switchOnForRetina.isOn == YES)
{
_imageDisplay.image = nil;
NSString* pResourcePath = [ [ NSBundle mainBundle ] pathForResource: [ NSString stringWithFormat: #"HyperReal_%i", ( int )_sliderBar.value ] ofType: #"png" ];
_imageDisplay.image = [ UIImage imageWithContentsOfFile: pResourcePath ];
}
else
{
_imageDisplay.image = nil;
NSString* pResourcePath = [ [ NSBundle mainBundle ] pathForResource: [ NSString stringWithFormat: #"lameHyperReal_%i", ( int )_sliderBar.value ] ofType: #"png" ];
_imageDisplay.image = [ UIImage imageWithContentsOfFile: pResourcePath ];
}
}
A better solution here would be to create 2 h264 movies from your images sequence
one in forward animation order, second in backward animation order
then play right movie depending on slider animation
Of course that's a lot more work.
Cheers
I'm not quite sure why you need to programmatically switch between Retina and non-Retina, as #2x handles that really well, but I'll assume there is a usage case I'm not familiar with and will leave it at that.
Regarding the memory usage, we're working on an app with similar functionality, and our solution was to populate an array with strings that refer to the image names (or file paths) of all the individual frames. In your case, you would store the reference myImageString rather than myImageGraphic.
Then when you drag your slider, you'd use something like this:
- (IBAction)changeSlider:(id)sender
{
if (_switchOnForRetina.isOn == YES)
_imageDisplay.image = [UIImage imageNamed:[_arrayRetina objectAtIndex:_sliderBar.value]];
else
_imageDisplay.image = [UIImage imageNamed:[_arrayNormal objectAtIndex:_sliderBar.value]];
}
One additional point, we use imageWithContentsOfFile due to lower memory usage as per this post, though imageNamed's caching may help in this case.
EDIT:
As mccrager pointed out, the above method only changes on touch up. Here's an example I'm using, though I'm using UIGestureRecognizer on a UIView to send a percent to the method rather than relying on a slider.
- (void)dragPosition:(float)myPercent
{
NSInteger index = (self.images.count - 1) * myPercent;
NSString *imageName = [self.images objectAtIndex:index];
NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:#"jpg"];
self.image = [UIImage imageWithContentsOfFile:imagePath];
}
Hope this helps!

Get UIImageView programmatically from View.

I'm creating a UIImageView within the code and I want to get it later on and set an image. How do I do this please help?
for (int x = 1; x <= 5; x++) {
UIImageView *Game_ImageViews = [[UIImageView alloc]
initWithFrame:CGRectMake(0, 0, 100, 100)];
Game_ImageViews.tag = x;
[Game_View addSubview:Game_ImageViews];
}
UIImage *image = [[UIImage alloc] initWithContentsOfFile:[[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent:#"Test.png"]];
(UIImageView*)[self.view viewWithTag:100].image = image;
[image release];
viewWithTag will give you the view with the tag if a subview with that tag exists. So for example, your self.view would have to have a subview that has a tag of 100.
Initially, you are setting up subviews of Game_View with tags of 1, 2, 3, 4, 5.
So, to retrieve a subview by tag you would have to do, assuming you want to retrieve the view tagged by id 1:
((UIImageView*)[Game_View viewWithTag:1]).image = image;
A couple of other things worth mentioning. You're not releasing Game_ImageViews in your loop above, so that will give a memory leak.
Also, if you use the imageNamed method of UIImage, that will give you a couple of benefits. First, you'll have a cached image which could improve performance. Secondly, you'll have an autoreleased object which saves you the bother of releasing it yourself. Unless of course you want maximum performance and control by doing it as you have done above.

Resources