Memory Leak Warnings, Due To NSArray Of Large Images - iphone

I have an iPad app, which is children's book. You navigate from one page to the next by calling this action, which essentialy fades out the current page and fades in the next page, then fills the three stack UIImageViews with the next row of the NSArray, so it's prepared to complete the action for the next page:
-(void)delayedMethod{
[leftButton setAlpha:1];
[pageImageNext setAlpha:0];
[pageImage setAlpha:1];
[pageImageBack setAlpha:1];
NSString *imageExtension = #".jpg";
NSString *audioExtension = #".wav";
if (activePage == thePages.count/3)
{
activePage = 1;
}
else
{
activePage = activePage + 1;
}
NSInteger row = 0;
if(activePage == 0)
{
row = activePage;
}
else
{
row = ((activePage) * 3);
}
[leftButton setAlpha:1];
pageImageBack.image = [UIImage imageNamed:[NSString stringWithFormat:#"%#%#", [thePages objectAtIndex:row+0], imageExtension]];
pageImage.image = [UIImage imageNamed:[NSString stringWithFormat:#"%#%#", [thePages objectAtIndex:row+1], imageExtension]];
pageImageNext.image = [UIImage imageNamed:[NSString stringWithFormat:#"%#%#", [thePages objectAtIndex:row+2], imageExtension]];
[UIImageView beginAnimations:NULL context:NULL];
[UIImageView setAnimationDuration:.5]; // you can set this to whatever you like
/* put animations to be executed here, for example: */
[leftButton setAlpha:0];
[rightButton setAlpha:0];
[leftButton setAlpha:1];
[rightButton setAlpha:1];
leftButton.hidden = NO;
/* end animations to be executed */
[UIImageView commitAnimations]; // execute the animations listed above
[imageExtension release];
}
The NSArray is loaded in this action, which is called in the ViewDidLoad:
-(void)loadPages
{
thePages = [[NSArray alloc] initWithObjects:
// How many stacked images do we need to get fade in and outs? Also add column fo
#"page0",#"page0",#"page1",
#"page0",#"page1",#"page2",
#"page1",#"page2",#"page3",
#"page2",#"page3",#"page4",
#"page3",#"page4",#"page5",
#"page4",#"page5",#"page6",
#"page5",#"page6",#"page7",
#"page6",#"page7",#"page8",
#"page7",#"page8",#"page9",
#"page8",#"page9",#"page10",
#"page9",#"page10",#"page11",
#"page10",#"page11",#"page12",
#"page11",#"page12",#"page13",
#"page12",#"page13",#"page14",
#"page13",#"page14",#"page15",
#"page14",#"page15",#"page16",
#"page15",#"page16",#"page17",
#"page16",#"page17",#"page18",
#"page17",#"page18",#"page19",
#"page18",#"page19",#"page20",
#"page19",#"page20",#"page21",
#"page20",#"page21",#"page22",
#"page21",#"page22",#"page23",
#"page22",#"page23",#"page24",
#"page23",#"page24",#"page25",
#"page24",#"page25",#"page26",
#"page25",#"page26",#"page27",
#"page26",#"page27",#"page27",
nil];
}
It all works just fine until about the 10-12th page when I start to get memory warnings in the console and usually a crash.
I am pretty sure it is just a matter of releasing the three large UIImageViews at the right time, but I can't figure out when...I've tried a number of different spots in the code.

I was doing an app like this, where I had a lot of image views and labels that the user could change. I got warnings and crashes all the time. The only thing that seemed to fix it all was to make them all properties. Everything I wanted to hang on to on a certain view I had to make a property. That took care of it nicely, otherwise it seemed like the OS freaked out that I had to much allocated, and would send memory warnings which would release stuff, and then when the app tried to access them it would crash. See if making your pageImageNext, pageImage , pageImageBack properties works.

Make sure you are doing a [thePages release]]; in the dealloc method of the view controller. Not releasing that will definitely cause memory issues.
Also have you made the imageviews retained properties in the view controller? If so, you need to release them in the dealloc as well.
As for memory warnings, you can unset the imageviews in didRecieveMemoryWarning and then just add in a check to re-create them as needed.

The code:
[imageExtension release];
Is incorrect and should be deleted. "imageExtension" is allocated on the stack, and will go away when the method exits. Only release things that you have alloc'd.
Do you have just one of these types of ViewControllers? Or one for each page?

#sasquatch #YuzaKen #MishieMoo If I simply set self.pageImage = nil; in the dealloc, as you suggested, won't that only release the memory when a user leaves the ViewController?
I feel like I need to be releasing the UIImageViews as I move down the rows of the array, when I replace the contents of the UIImageView with a new image, no?

Related

Toggle between two images with sleeptime

I need to toggle a specific amount of times between two (or maybe later on more) pictures after a button was pressed, and wait a second or two for the change. When a stop-button is pressed at any time, the toggling should stop. My code by now looks like this
IBOutlet UIImageView *exerciseView;
- (void) repetitionCycle {
stopButtonPressed = NO;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
for (NSInteger counter = kRepetitions; counter >=0; counter--) {
exerciseView.image = [UIImage imageNamed:#"blue.jpg"];
[NSThread sleepForTimeInterval:kSleepDuration];
if (stopButtonPressed) {break;}
image = [UIImage imageNamed:kExerciseEndingPosition];
exerciseView.image = [UIImage imageNamed:#"image1.jpg"];
[NSThread sleepForTimeInterval:kSleepDuration];
if (stopButtonPressed) {break;}
}
self.stopRepetitionCycle;
[pool release];
}
exerciseView is
Besides other things, in stopRepetitionCycle I just set stopButtonPressed to YES, so it stops after it finished the "for" for the first time.
It does count down, it does stop after one cycle, but it doesn't change the picture.
While fiddling around, I set the initial picture via IB, so it finally displayed ANYTHING.. The fun part, when I hit the stop button in the right moment, the second picture is shown. So I guessed I need to set the view manually every time the image should toggle. But
[self.exerciseView addSubview:image];
gives me the error
Incompatible Objective-C types "struct UIImage *", expected "struct UIView *" when passing argument 1 of "addSubview:" from distinct Objective-C type
Also
[self.exerciseView.image addSubview:image];
doesn't do the job and gives me a
UIImage may not respond to addSubview
Any idea what I have to do here?
Thank you very much!
uuhm...
your usage of [NSThread sleep...] puzzles my...
in fact: if you are on a secondary thread (meaning, not the main thread), then you are doing something not allowed, which is trying to access the UI from a secondary thread.
this could explain the strange behavior you are seeing.
on the other hand, if this is the main thread, possibly is not a good idea to call sleep... because that way you will freeze entirely the UI, and you could not possibly intercept the click on the button...
anyhow, what I would suggest, is using NSTimers to move from one image to the next one at certain intervals of time. when the stop button is hidden you would simply cancel the timer and the slideshow would end. pretty clean.
as to the error messages you are having with your image, the fact is that UIImage is not a UIView, so you cannot add it as a subview, but this is not what does not work here...
Use UIImageView. It has built-in support for this.
imageView.animationImages = [NSArray arrayWithObjects:[UIImage imageNamed:#"blue.jpg"], [UIImage imageNamed:#"image1.jpg"], nil];
imageView.animationDuration = kSleepDuration * [imageView.animationImages count];
Wire this function to the start button
- (IBAction) startAnimation {
[imageView startAnimating];
}
and this to the stop button
- (IBAction) stopAnimation {
[imageView stopAnimating];
}
Updating the UI within a loop is almost guaranteed to fail. You should most likely be using an NSTimer to swap the image at a given interval instead.
Furthermore, [self.exerciseView addSubview:image] is failing because you're passing a UIImage and not a UIView. Create a UIImageView (which is a subclass of UIView) with your UIImage and pass that instead.
The method addSubview is used to add only UIViews. You can't use it with a UIImage class. The general syntax is :
[(UIView) addSubview:(UIView*)];
replace
[self.exerciseView addSubview:image];
with
self.exerciseView.image = image;
For the same reason
[self.exerciseView.image addSubview:image];
won't work either.

How do i properly discard a subview?

I have a problem with my app where the code for which is far too long to go into, but suffice to say when i'm removing a UIView and replacing it with a new one like so:
NSLog(#" .. %#", (Icon *)[self viewWithTag:index]);
Icon *icon = (Icon *)[self viewWithTag:index];
CGRect frame = icon.frame;
int tag = icon.tag;
[icon removeFromSuperview];
[icon release];
Icon *icon2 = [[Icon alloc] init];
icon2.frame = frame;
[icon2 makeIconStandardWithTag:(int)tag];
[self addSubview:icon2];
It does some weird thing where that NSLog the first time (because the view is already there) shows that the object is an icon, but the second time after running this code shows that it's a UIImageView for some reason now, and it displays what i presume to be the original icon at some odd position on the screen. It's very erratic behaviour. But what i do know is this:
Removing the [icon removeFromSuperview]; line, although keeping the object there, stops this behaviour and causes the NSLog to return an Icon, as it should.
So my guess is that it's not removing icon correctly. Is there a way to completely remove icon, or is removeFromSuperview as far as i can go. What i could do is just have it set to alpha = 0 but this is more of a patch-over solution and not how i want to solve it.
"Is there a way to completely remove
icon, or is removeFromSuperview as far
as i can go"
You can set the object to nil:
icon = nil;
Can you verify what "self" is in this line of code:
It might not be what you think.
[self addSubview:icon2];
NSLog(#" Self is %#", self);
This is a guess, but try setting self.tag to -1 or some other value that doesn't collide with the tags you're setting on your Icon objects. The viewWithTag: method searches the current view and its subviews for a match, so if self.tag == 0 and you call [self viewWithTag:0], you'll get self.
Did you retain icon somewhere prior to this? If not, no need to release it after the call to removeFromSuperview. Similarly, unless you need the reference to icon2 elsewhere, you can release that after calling addSubview.
Views retain views added via addSubview, and they release views removed via removeFromSuperview.

UIScrollView with UIImageView, UIImage

I've read a lot of UIScrollView with UIImageView threads here or other googled pages. But I still cannot get the problem I'm confronting. I'm having a cold right now. Hope I can still make it clear, lol. Here is the problem:
I'm building one app which mainly uses UIScrollView to display a few images. Here the amount counts, not the size, which is averagely 100KB(I even converted PNG to jpg, not sure whether it helps or not). With no more than 10 images, my app crashes with memory warning. This is the first time I encounter memory issue, which surprised me as the compiled app is less than 10MB.
At the very beginning, I load all the images on launch, looping all names of image files and do
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:imgName]];
[scrollview addSubview:imageView];
[imageView release];
If I'm right, I think after launch, all the images are in memory, right? But the funny thing here is, the app could launch without any problem(at most a level 1 memory warning). After I scroll a few images, it crashed. I checked leaks for sure and also allocations. No leak and allocation almost had no change during scrolling.
So, is there something special done by imageNamed rather than cache?
And then, yes, I turned to lazy load.
For fear of checking page and loading images on demand might jerk the scrolling(which was proved true), I used a thread which runs a loop to check offset of the scroll view and load/unload images.
I subclassed UIImageView by remembering the image name. It also contains loadImage and unloadImage which will be executed on that thread.
- (void)loadImage {
/if ([self.subviews count] == 0) {
UIImageView iv = [[UIImageView alloc] initWithImage:[UIImage imageNamed:self.imageName]];
[self performSelectorOnMainThread:#selector(renderImage:) withObject:iv waitUntilDone:NO];
//[self addSubview:iv];
[iv release];
}*/
if (self.image == nil) {
//UIImage *img = [UIImage imageNamed:self.imageName];
UIImage *img = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:[self.imageName stringByDeletingPathExtension] ofType:[self.imageName pathExtension]]];
// image must be set on main thread as UI rendering is main thread's responsibility
[self performSelectorOnMainThread:#selector(renderImage:) withObject:img waitUntilDone:NO];
[img release];
}
}
// render image on main thread
- (void)renderImage:(UIImage*)iv {
//[self addSubview:iv];
self.image = iv;
}
(void)unloadImage {
self.image = nil;
//[(UIView*)[self.subviews lastObject] removeFromSuperview];
}
You can see the commented code that I've played with.
In unloadImage, if I write [self.image release], then I get EXC_BAD_ACCESS, which is unexpected, as I think alloc and release are matched here.
The app still crashes with no leak. The initWithContentsOfFile version even crashed earlier than imageNamed version, and made the scrolling not that smooth.
I run the app on device. By checking allocations, I found imageNamed version used much less memory than initWithContentsOfFile version, though they both crash. Instruments also showed that the allocated images were 2,3 or 4, which indicated the lazy load did do his job.
I checked PhotoScroller of WWDC2010, but I don't think it solvs my problem. There is no zooming or huge picture involved.
Anybody helps! Thank you in advance.
The crash log says nothing. The app crashes mostly after memory warning level = 2. And if run on simulator, there will be no problem.
It doesn't matter which format do you use for your images. They're converted to bitmaps when you display them.
I'd suggest to use the technique similar to that one which is used by UITableView (hide the image and free the memory it uses when it disappears from the screen and instantiate the image only when you need to show it).
As an alternate way – if you need to show these images in a grid – you might take a look to a CATiledLayer.
Anyhow, loading all the images to the memory is not the best idea :)
You can load all the images to an array. And you can design a view having one image view and try the below code:
array name: examplearray and view name :exampleview
-(void)updateImagesInScrollView
{
int cnt = [examplearray count];
for(int j=0; j< cnt; ++j)
{
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"exampleview"
owner:self
options:nil];
UIView *myView = [nibContents objectAtIndex:0];
exampleview * rview= (exampleview *)myView;
/* you can get your iamge from the array and set the image*/
rview.imageview.image = yourimage;
/*adding the view to your scrollview*/
[self.ScrollView addSubview:rview];
}
/* you need to set the content size of the scroll view */
self.ScrollView.contentSize = CGSizeMake(X, self.mHorizontalScrollView.contentSize.height);
}

Problem releasing object using "removeFromSuperView" iPhone

I have this concrete problem, but if You find my initial design idea crazy and have a better suggestion, please let me know:)
I have a UIView that acts as a container/background for adding other views. The important thing is that only one view is present at a time. So before doing any adding of views I do this:
for(UIView *v in [self.currentView subviews]) {
[v removeFromSuperview];
}
self.currentView is the view I add my subviews to.
After this I add a new UIView in this manner:
UIView *tempView;
switch (self.currentIndex) {
case 1:
tempView = [[AView alloc] initWithFrame:frame];
[self.currentView addSubview:tempView];
[tempView release];
break;
case 2:
tempView = [[AView alloc] initWithFrame:frame];
[self.currentView addSubview:tempView];
[tempView release];
break;
default:
break;
}
This way I remove all views, since I release the tempView straight after I add it to
the self.currentView I end up with a retain count of one on the the UIView currently living
in the currentView.
This all seems fine, but as I look at it with Instruments I can see that each time I run the above code a new AView object is allocated and the old one keeps hanging around with a retain count of 1, either I am missing some obvious retain action being performed on my object or else the "removeFromSuperView" does not call "release" on my view.
In real life my objects are not of type AView, but of many different types, but this way I can test if there is always only one AView instance allocated.
As I can read from the documentation "removeFromSuperView" should call "release" on the view so I am a bit confused as to why my Views are not deallocated.
Again, maybe I am going about this the wrong way and suggestions are really welcome.
The setup is that there is a number of button at the bottom of the screen and when the user clicks on the view changes.
Thanks for any help or pointers given:)
You are iterating a collection and simultaneously changing it
Try
while ([self.currentView subviews].count>0) {
[[[self.currentView subviews] objectAtIndex:0] removeFromSuperView];
}
instead.
you could try the "bringSubviewToFront" and "sendSubviewToBack" functions instead of creating a new UIView everytime. That ways, you won't be creating uiviews for every action and therefore be less pressing on the memory consumption of your application.

ImageIO initImageJPEG instances getting allocated and never released

Im developing an app for an iPhone and I found that the following code is causing the memory allocation to increment.
-(UIImage *)createRecipeCardImage:(Process *)objectTBD atIndex:(int)indx
{
[objectTBD retain];
// bringing the image for the background
UIImage *rCard = [UIImage imageNamed:#"card_bg.png"];
CGRect frame = CGRectMake(00.0f, 80.0f, 330.0f, 330.0f);
// creating he UIImage view to contain the recipe's data
UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
imageView.image = rCard;
[rCard release];
imageView.userInteractionEnabled = YES;
float titleLabelWidth = 150.0;
float leftGutter = 5.0;
float titleYPos = 25.0;
float space = 3.0;
float leftYPos = 0;
// locating Title label
float currentHeight = [self calculateHeightOfTextFromWidth:objectTBD.Title :titleFont :titleLabelWidth :UILineBreakModeWordWrap];
UILabel *cardTitle = [[UILabel alloc]initWithFrame:CGRectMake(leftGutter, titleYPos, titleLabelWidth, currentHeight)];
cardTitle.lineBreakMode = UILineBreakModeWordWrap;
cardTitle.numberOfLines = 0;
cardTitle.font = titleFont;
cardTitle.text = objectTBD.Title;
cardTitle.backgroundColor = [UIColor clearColor];
[imageView addSubview:cardTitle];
[cardTitle release];
leftYPos = titleYPos + currentHeight + space;
// locating brown line
UIView *brownLine = [[UIView alloc] initWithFrame:CGRectMake(5.0, leftYPos, 150.0, 2.0)];
brownLine.backgroundColor = [UIColor colorWithRed:0.647 green:0.341 blue:0.122 alpha:1.0];
[imageView addSubview:brownLine];
[brownLine release];
leftYPos = leftYPos + 2 + space + space + space;
// creating the imageView to place the image
UIImageView *processPhoto = [[UIImageView alloc] initWithFrame:CGRectMake(leftGutter, leftYPos, 150, 150)];
if((uniqueIndex == indx) && (uniqueImg.imageData != nil))
{
if([uniqueImg.rcpIden isEqualToString:objectTBD.iden])
{
objectTBD.imageData = [NSString stringWithFormat:#"%#", uniqueImg.imageData];
[recipesFound replaceObjectAtIndex:indx withObject:objectTBD];
NSData * imageData = [NSData dataFromBase64String:objectTBD.imageData];
UIImage *rcpImage = [[UIImage alloc] initWithData:imageData];
[imageData release];
processPhoto.image = rcpImage;
[rcpImage release];
}
}
else if(objectTBD.imageData != nil)
{
NSData * imageData = [NSData dataFromBase64String:objectTBD.imageData];
UIImage *rcpImage = [[UIImage alloc] initWithData:imageData];
processPhoto.image = rcpImage;
[rcpImage release];
[decodedBigImageDataPointers addObject:imageData];
}
else
{
UIImage * rcpImage = [UIImage imageNamed:#"default_recipe_img.png"];
processPhoto.image = rcpImage;
[rcpImage release];
}
NSlog(#" Process Photo Retain Count %i", [processPhoto retainCount]); // this prints a 1
[imageView addSubview:processPhoto];
NSlog(#" Process Photo Retain Count %i", [processPhoto retainCount]); // this prints a 2!!!!
//[processPhoto release]; // this line causes an error :(
// converting the UIImageView into a UIImage
UIGraphicsBeginImageContext(imageView.bounds.size);
[imageView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[objectTBD release];
for(UIView *eachSubview in imageView.subviews)
{
[eachSubview removeFromSuperview];
NSLog(#"each subview retainCount %i despues", [eachSubview retainCount]);
// here I find that the processPhoto view has a retain count of 2 (all other views have their retain count in 1)
}
return viewImage;
}
When I checked at the instruments object allocation I found that the "GeneralBlock-9216" growing up.
Breaking down the row I found that every time I call this code, one instance of:
2 0x5083800 00:18.534 ImageIO initImageJPEG
is being allocated. Checking the call stack, the following line is highlighted:
UIImage * objImage = [UIImage imageWithData:imageData];
Any help to find what the error is?
As TechZen said, the imageWithXXX: methods cache the image inside of them while you run the program (though you release the instances after using). I recommend initWithXXX: and release API sets instead of imageWithXXX:.
Well, if you embed several debug log on your source code, check how many times is it called, and check the retain count of the instances.
As far as I can explain, that is all.
I hope you will solve the problem.
Does anyone have an answer for this? It's tearing me apart trying to figure out why this image information keeps lingering. I've tried every solution.
The situation:
Images get downloaded and stored to the device, then loaded with imageWithContentsOfFile (or even initWithContentsOfFile, which doesn't help either). When the view goes away, the images don't, but they don't show up as leaks, they're just this initImageJPEG Malloc 9.00 KB that never goes away and keeps ramping up.
UPDATE: I believe I've figured this out: Check to make sure everything is actually getting dealloc'd when you're releasing whatever the parents (and/or grandparents) and etc of the images are. If the parents don't get deallocated, they never let go of their children images, and whatever data was in those images sticks around. So check retain counts of parent objects and make sure that everything's going away all the way up whenever you release the view at the top.
A good way to check for this is to put NSLogs into custom classes' dealloc methods. If they never show up, that object isn't going away, even though the reference to it might, and it (and whatever its subviews and properties are) will never ever disappear. In the case of images, this means a pretty sizable allocation every time that object is generated and never deallocated. It might not show up in leaks, especially if the parent of the topmost object you're thinking you're releasing but actually aren't persists and doesn't itself ever deallocate.
I hope this helps. It'll be useful to take some time to read through your code with a fine-toothed comb to make sure you're allocating and releasing things properly. (Search for "alloc]", start at the top of the file, and work your way down to make sure you're releasing and that the release isn't inside of some if() or something.)
Also, running "Build and Analyze" might lock up your machine for a bit, but its results can be really helpful.
Good luck!
I think you're seeing UIImage cacheing images. There used there used to be a method something like initWithData:cache that let you turn the cacheing off. Now I think the system always caches the images automatically behind the scenes even after you've deallocted the specific instances.
I don't think its an error on your part. I think it's the system keeping data around in the OpenGl subsystem. Unless it causes a major leak, I don't think it is a problem.