I have got an strange problem but I am quite sure, for you it is not. If it is so, please help me or explain.
I have an array of 39 UIImages which will be used for animating UIImageView. and after playing the animation, memory does not release.
- (void)viewDidLoad {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
NSMutableArray* actionArray = [[NSMutableArray alloc]initWithCapacity:38];
for (int i=1; i<=39; i++) {
NSString* path = [[NSBundle mainBundle]pathForResource:[NSString stringWithFormat:#"bg_Level1_%.4d", i] ofType:#"png"];
UIImage *image = [[UIImage alloc]initWithContentsOfFile:path];
[path release];
[actionArray addObject:image];
[image release];
NSLog(#"path rc %d image %d", [path retainCount], [image retainCount]);
}
imageView.animationImages = actionArray;
imageView.animationDuration = 4.0;
imageView.animationRepeatCount = 1;
[imageView startAnimating];
NSLog(#"RetainCount ActArr %d ImageView %d", [actionArray retainCount], [imageView retainCount]);
[actionArray release];
[pool drain];
[imageView release];
[self performSelector:#selector(playAnimation2) withObject:self afterDelay:5.0];
[super viewDidLoad];
}
-(void)playAnimation2{
if ([imageView isAnimating]) {
[imageView stopAnimating];
}
NSLog(#"RetainCount ImageView %d",[imageView retainCount]);
}
there is no leaks discovered but the amount of memory allocated still is 24 mb.
or it is not so necessary ?
EDIT:
Sorry, my fault.
Due to many changes of my code I've got lost in it. After analysing I get it!
Thanks a lot!
The correct Code is:
- (void)viewDidLoad {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
NSMutableArray* actionArray = [[NSMutableArray alloc]initWithCapacity:38];
for (int i=1; i<=39; i++) {
NSString* path = [[NSBundle mainBundle]pathForResource:[NSString stringWithFormat:#"bg_Level1_%.4d", i] ofType:#"png"];
UIImage *image = [[UIImage alloc]initWithContentsOfFile:path];
// [path release];
[actionArray addObject:image];
[image release];
NSLog(#"path rc %d image %d", [path retainCount], [image retainCount]);
}
imageView.animationImages = actionArray;
imageView.animationDuration = 4.0;
imageView.animationRepeatCount = 1;
[imageView startAnimating];
NSLog(#"RetainCount ActArr %d ImageView %d", [actionArray retainCount], [imageView retainCount]);
[actionArray release];
[pool drain];
[self performSelector:#selector(playAnimation2) withObject:self afterDelay:5.0];
[super viewDidLoad];
}
-(void)playAnimation2{
if ([imageView isAnimating]) {
[imageView stopAnimating];
}
[imageView removeFromSuperview];
imageView.animationImages = nil;
NSLog(#"RetainCount ImageView %d",[imageView retainCount]);
}
The problem was with [path release];
There are quite a few things wrong with this code. It is a wonder it runs at all.
First:
Do not use -retainCount.
The absolute retain count of an object is meaningless.
You should call release exactly same number of times that you caused the object to be retained. No less (unless you like leaks) and, certainly, no more (unless you like crashes).
See the Memory Management Guidelines for full details.
Now, some of the problems:
You asked if you need to empty the array and then said array=nil; ... doesn't work. Setting an array reference to nil doesn't do anything to the array; doesn't release it or empty it.
you are overreleasing path
you are loading a series of images, sticking 'em in an array, and there isn't anything in that code that is releasing the array (or emptying it). That your app continues to use the memory and shows no leaks sound like correct behavior.
Why are you releasing imageView? There is nothing in that code implying that you retained it and, even if it were retained via some other mechanism (IB?), that would be a really odd place to release it.
I think you need to remove all the objects from the array for it to work.
Try deleting the objects from the array, and then the array once you're finished with them.
You are releasing imageView without retaining it anywhere, so there is probably something else wrong with you code. It seems however, that imageView is retained by another object ( most likely the superview), which keeps it in memory. This will also keep the array of images in memory.
If you don't need imageView anymore, remove it from its parent with [imageView removeFromSuperview];
As we already solved here: releasing problems
Set imageView.animationImages = nil;
Also, you need to remove the [path release]; or it will crash.
The Memory Management Guide is a vital place to start with.
Edit Never rely on the retainCount property!
Edit 2 Releasing imageView in that method is clearly a bug as well!
Related
I have a table view and I'd like to download an icon image (100x75) to each row asynchronously. I've followed many tutorials so far but I can't seem to figure it out. How should I do it?
Does anyone recommend just doing it using the standard NSURLConnection API or should I use one of those classes/libraries that are available online to do so? If so, what do you recommend?
Of course, I need it to be fast, efficient and does not leak. I also don't want the downloading to affect the scrolling.
Thank you!
Two options I can think of:
(1) Use ASIHTTPRequest.
(2) A custom implementation that spawns a thread in which you load the image using a combination of NSURL/NSData. Once the image is loaded, send it to a method on the main UI thread using performSelectorOnMainThread:withObject:.
NSThread *t = [[NSThread alloc] initWithTarget:self selector:#selector(loadImage:) object:nil];
[t start];
[t release];
-(void) updateImage:(id) obj {
// do whatever you need to do
}
-(void) loadImage:(id) obj {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSURL *url = [NSURL URLWithString:#"imageurl"];
NSData *imageData = [[NSData alloc]initWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:imageData];
[imageData release];
[self performSelectorOnMainThread:#selector(updateImage:) withObject:data waitUntilDone:YES];
[pool release];
}
I'd recommend you using ASIHTTPRequest. Its simple and pretty fast.
Here is a link to the documentation - ASIHTTPRequest
EDIT
You need to download images for visible cells only.
Heres a sample:
- (void)loadContentForVisibleCells
{
NSArray *cells = [self.tableView visibleCells];
[cells retain];
for (int i = 0; i < [cells count]; i++)
{
...
// Request should be here
...
}
[cells release];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
{
if (!decelerate)
{
[self loadContentForVisibleCells];
}
}
Anyway u'll need to code a lot to make things work good and fast.
So I first coded all my methods in a viewcontroller with an NSOperationQueue. After doing some research and a lot of reading I realized i had to subclass my loadImage operation so that I may use isCancelled and cancelAllOperations. So I went ahead and created an nsoperation class and called it from my viewcontroller. ALl the methods are called, even the imageLoaded, but the NSMutableDictionary remains empty. I use the dictionary to populate my tableviewcells using the url as the Key. Also be aware that the operation call in the viewcontroller is within a method which is called by an NSInvocationOperation when the view loads.
#interface loadImages : NSOperation {
NSURL *targetURL;
}
#property(retain) NSURL *targetURL;
- (id)initWithURL:(NSURL*)url;
#end
implementation of nsoperation class which includes some other calls to resize the image
#implementation loadImages
#synthesize targetURL;
- (id)initWithURL:(NSURL*)url
{
if (![super init]) return nil;
[self setTargetURL:url];
return self;
}
- (void)dealloc {
[targetURL release], targetURL = nil;
[super dealloc];
}
- (void)main {
NSLog(#"loadImages.m reached");
StoriesTableViewController *stories = [[StoriesTableViewController alloc] init];
NSMutableDictionary *tempDict = stories.filteredImagesDict;
UIImage *myImage = [[[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[self targetURL]]]autorelease];
UIImage *scaledImage = [[[UIImage alloc] init] autorelease];
CGRect frame = CGRectMake(100.0f, 100.0f, 180.0f, 180.0f);
UIImageView *myImageFrame = [[UIImageView alloc] initWithFrame:frame];
myImage = [[myImage croppedImage:[myImageFrame bounds]]retain];
scaledImage = [[myImage resizedImage:CGSizeMake(120.0f, 120.0f) interpolationQuality:kCGInterpolationHigh]retain];
[tempDict setValue:scaledImage forKey:[self targetURL]];
[stories performSelectorOnMainThread:#selector(imageLoaded:)
withObject:myImage
waitUntilDone:YES];
NSLog(#"targetURL %#",[self targetURL]);
NSLog(#"tempDict count: %d",tempDict.count);
[stories release];
[myImage release];
[myImageFrame release];
[scaledImage release];
}
creation of nsoperation on viewcontroller
for(int i=0;i<storyQuantity;i++) {
NSString *imageString = [[[storiesArray objectAtIndex:i] objectForKey: #"image"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; // must add trimming to remove characters
NSURL *url = [NSURL URLWithString:imageString];
loadImages *imageOperation = [[loadImages alloc] initWithURL:url];
[queue_ addOperation:imageOperation];
[imageOperation release];
}
If this is running without exceptions and the dictionary is still empty, it most likely means that your value is nil.
This is a common problem with code like that where you have the result of one method going into the next. At any point if there is a problem, all the rest of the chain will not work.
To solve, I would start right above where you assigned the image to the dictionary. You can use either a breakpoint, or an NSLog to determine the value of the image at that point. I prefer to use an NSLog, but a break point would let you look at all the values at once. If scaledImage is nil, then check myImage. Keep going back up the chain until you find the point where the value goes from what you would expect, to nil.
When I analyze the following code with Instruments, it reports a leak on variable imageName:
//loadImagesFromPotatoesIndexesArray
-(void) loadImagesFromPotatoesIndexesArray{
//Load Textures from Disk
textures = [[NSMutableArray alloc] init];
//NSArray *masks = [[NSArray alloc] initWithArray:mainDelegate.masksArray];
for (int i = 0;i<[potatoesIndexesArray count];i++){
int imageNumber = [[potatoesIndexesArray objectAtIndex:i]intValue];
NSString *imageName = [[NSString alloc] initWithFormat:#"texture%d",imageNumber];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:imageName ofType:#"png"]];
NSArray *pics = [[NSArray alloc] initWithObjects:
[self maskImage:image withMask:[mainDelegate.masksArray objectAtIndex:i]],
[self maskImage:image withMask:[mainDelegate.masksArray objectAtIndex:i]],
imageName,
nil];
[textures addObject:pics];
[image release];
[imageName release];
[pics release];
}
}
[potatoesIndexesArray count] = 16, so I've got 16 times that NSCFString leaking ... But to me the code is respecting memory management ... obviously not!!!
What did I do wrong?
You never release the 'textures' array. It's still holding everything.
How often is loadImagesFromPotatoesIndexesArray called in your code? If is called more than once, all of the values in the original array will be leaked, since you don't properly release textures before replacing it with a new array.
If it is being called more than once, this should do the trick:
// load textures from disk
[textures removeAllObjects];
//NSArray *masks = [[NSArray ...
for (int i=0; ...
If think that when you add imageName in your pics array it retain it ;-)
(I think it answer to your question)
But, why are you doing a alloc here ?
why not doing something like
[ NSString stringWithFormat:#"" ] ?
Good Luck !
This is a complicated issue. You alloc the imageName, so the retainCount is 1, then you add it into an array, the retain count is 2, when you release the imageName, the retain Count is 1 again. Then if you also release the pics array, everything will be fine. But your pics array is added into textures, then the pics is released, so your pics retainCount is still 1. And your imageName is leaked. But, if you release the textures array, everything will be fine
NSString *imageName = [[NSString alloc] initWithFormat:#"texture%d",imageNumber];
NSArray *pics = [[NSArray alloc] initWithObjects:
[self maskImage:image withMask:[mainDelegate.masksArray objectAtIndex:i]],
[self maskImage:image withMask:[mainDelegate.masksArray objectAtIndex:i]],
imageName,
nil];
[imageName release];
where to dealloc/ release my NS-mutable array a1 ??
see this
- (void)viewDidLoad {
[NSThread detachNewThreadSelector:#selector(loadImage) toTarget:self withObject:nil];
}
- (void) loadImage
{
NSLog(#" THREAD METHOD");
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSUserDefaults *imgg = [NSUserDefaults standardUserDefaults];
myimg= [imgg stringForKey:#"keyToimg"];
NSLog(#"RES image sssssssss is = %#",myimg);
a1 = [[NSMutableArray alloc] init];
[a1 addObjectsFromArray:[myimg componentsSeparatedByString:#"\n\t"]];
//[a1 removeAllObjects];
////
//[myimg release];
[pool release];
}
and in table cell of secition 3 i am displaying image
switch(indexPath.section)
{
NSString *urlE=[a1 objectAtIndex:1];
NSLog(#"url is %#",urlE);
NSData *backgroundData = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlE]];
image = [UIImage imageWithData:backgroundData];
myImageView= [[UIImageView alloc] initWithImage:image];
[myImageView setUserInteractionEnabled:YES];
CGRect rect=CGRectMake(20 ,10, 270, 180);
myImageView.frame = rect;
myImageView.tag = i;
[cell.contentView addSubview:myImageView];
}
and based on tap images are changing
pragma mark working image tap
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#" life count %d",[myimg retainCount]);
NSLog(#" life array count %d",[a1 retainCount]);
//NSLog(#" GITSHffffffffffffffffffffffffffffffC");
NSUInteger sections = [indexPath section];
//NSLog(#"row is %d",sections);
if (sections == 3)
{ //Its either 1 or 0 I don't remember, it's been a while since I did some tableview
if(tap<[a1 count]-1) {
NSLog(#" life array count %d",[a1 retainCount]);
tap++;
NSString *sa=[a1 objectAtIndex:tap];
//////////////////////
image= [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat: sa,[a1 objectAtIndex:tap ]]]]];
NSData *imageData = [NSData dataWithData:UIImageJPEGRepresentation(image, 1.0)];
myImageView.image = image;
//[myimg release];
//[a1 release];
}
else {
tap=1;
//[myimg release];
//[a1 release];
}
}
//[a1 release];
}
so where should i release my a1 and myimg
a1 will never be released using this code.
You should put it on a member variable or add autorelease after init.
By the way, your myImageView should be released after you add it to the cell view.
It is possible because of the retain/release logic: when you alloc the myImageView the retain count is +1, once you add it to cell view,it is now +2, you should then release it so that the retain comes back to +1 and then when cell view will be further deallocated, it will decrement the retain count to 0.
The same logic for the variable image in the last function
Regards
Meir assayag
Instead of :
a1 = [[NSMutableArray alloc] init];
[a1 addObjectsFromArray:[myimg componentsSeparatedByString:#"\n\t"]];
Consider:
a1 = [NSMutableArray arrayWithArray:[myimg componentsSeparatedByString:#"\n\t"]];
That'll initialize your a1 with an autoreleased NSMutableArray object, and then you don't have to worry about manually releasing it.
The thing I don't know is whether your [pool release] will release it, but... I'd really prefer you NOT put that business in a background thread, but rather use asynchronous network methods to get your image data.
By the way, as I was learning iPhone development, I went through three or four levels of "aha moments" about backgrounded networking. One of them had to do with running selectors on background threads. That lasted about a week until I discovered ASIHttpRequest, which is how I do it now. MUCH simpler way to put network interactions in the background without having to mess with threading or any of that nonsense. See http://allseeing-i.com/ASIHTTPRequest/
If you look at my answers, every time HTTP client networking comes up I recommend ASI. I really don't mean to be a shill for it--it's just made my life so much easier I think everyone needs to know about it.
I am changing the image in UIImageView based on accelerometer input. The images are stored in an array.
The application runs fine for a while and then crashes.
warning: check_safe_call: could not restore current frame
I am not using "UIImage ImageNamed" method when populating the array.
The total size of all images is around 12 Mb. But individual images are very light (<100 kb) and i am using only one image at a time from the array.
Just in case there are any autoreleases, I have allocated a NSAutoreleasePool in view did load and am draining it in the didReceiveMemoryWarning method (which does get called 2, 3 times before the app crashes?).
Following is the code that creates images array:
//create autorelease pool as we are creating many autoreleased objects
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSMutableArray *finalarr = [[NSMutableArray alloc] initWithCapacity:9];
NSLog(#"start loading");
for(int y = 0; y < 100; y+=10)
{
NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:10];
for(int x = 0; x < 10; x++)
{
NSString *imageName = [[NSString alloc] initWithFormat:#"0%d",y + x];
UIImage *img = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:imageName ofType:#"png"]];
[arr addObject:img];
[imageName release];
[img release];
}
[finalarr addObject:arr];
[arr release];
}
NSLog(#"done loading");
// Override point for customization after app launch
viewController.imagesArray = finalarr;
[finalarr release];
//retain the array of images
[viewController.imagesArray retain];
//release the aurtorelease pool to free memory taken up by objects enqued for release
[pool release];
As the app runs smoothly for a while, which means array creation is definitely not the problem.
After this the following method is called from [accelerometer didAccelerate]
-(BOOL)setImageForX:(int)x andY:(int)y
{
[self.imageView setImage:(UIImage*)[(NSArray*)[self.imagesArray objectAtIndex:y] objectAtIndex:x]];
return TRUE;
}
So the only code being executed is the "UIImageView setImage". But no objects are being created here.
Please let me know if the code seems to have any leaks or i am doing something wrong.
Thanks,
Swapnil
NSAutoreleasePool should be allocated and released in the same method. Allocating it in one place and releasing it in another is usually very bad (the example code you pasted uses it correctly however)
Loading images into memory will decompress them; a 100KB compressed PNG could easily be 2MB uncompressed.
You also seem to have an extra retain call that shouldn't be there: [viewController.imagesArray retain]; (I assume the imagesArray property is marked copy or retain and not assign)