Memory Management question - iphone

What is the best approach to release multiple instances of an object that you can't release right away?
For example, if you are creating a bunch of thumbnails:
for(int i=0; i <totalThumbs; i++){
Thumb *newThumb = [[Thumb alloc] initWithImage:someImage]
//position the thumbs here, etc.
//assume releasing here breaks the app because we need to interact with the thumbs later
// [newThumb release] --- breaks the app
}
Would it make sense to put all the new objects in an array and release them all in viewDidUnload when we no longer need them?

Presumably you are adding each newThumb as a subview of some other view or to an array, so you should be fine to do that and then release newThumb here. For example:
Thumb *newThumb = [[Thumb alloc] initWithImage:someImage];
[myThumbs addObject:newThumb];
[newThumb release];
This works becuase myThumbs retains the object.
In order not to leak the memory, especially if you regenerate the thumbnails, you would want to iterate over the superview's subviews (all the thumbs), remove each from the superview, and release them. You may also need to do this in you dealloc method where you release the superview (assuming you do that). With an array, you could simply call removeAllObjects, I believe.

Maybe I'm missing something, but why not use autoreleasepools?
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
for(int i=0; i <totalThumbs; i++){
Thumb *newThumb = [[[Thumb alloc] initWithImage:someImage]autorelease];
}
[pool drain];
Calling autorelease will add it to the pool (that you can create in any scope you like). Just call drain (or release) on the pool when you're done with it. This will release all queued objects.

You can release them right after adding to the array, because the array retains them:
for(int i=0; i <totalThumbs; i++){
Thumb *newThumb = [[Thumb alloc] initWithImage:someImage]
//position the thumbs here, etc.
[thumbsArray addObject:newThumb];
[newThumb release]; // --- doesn't break the app
}
In viewDidUnload and/or dealloc release the array. You don't need to release every single thumb.

We should always avoid allocating memory in a loop. In your case you should release memory immediately after using the object you have created. i.e
for(int i=0; i <totalThumbs; i++){
Thumb *newThumb = [[Thumb alloc] initWithImage:someImage];
//position the thumbs here, etc.
//assume releasing here breaks the app because we need to interact with the thumbs later
// [newThumb release] --- breaks the app
// Work with newThumb
[newThumb release];
}
By doing this the objects get released each time loop runs. Actually each time the loop runs, a new object is created. This is how you can manage memory allocation in a loop.
Cheers!

Related

Take an object out of a MutableArray and place it in a new object

Here is my code simplified:
NSMutableArray* buildBlocks = [[[NSMutableArray alloc] initWithCapacity:0] retain];
Block* selectedBlock = [[[Block alloc] init] retain];
// Add several blocks to "buildBlocks"
for( int i=0; i < [buildBlocks count]; i++)
{
Block* tempBlock = [buildBlocks objectAtIndex:i];
if( tempBlock.selected )
{
// Move the block to the selected block
selectedBlock = tempBlock;
// Take the block out of the array
[buildBlocks removeObjectAtIndex:i];
}
}
// Some code later
if( selectedBlock.selected ) // <---- Crashes here
{
// Do stuff
}
I want to copy the selected block to "SelectedBlock," delete the block out of the array, then use the "SelectedBlock" later. When I used this code I always get "EXC_BAD_ACCESS. I feel like the program is releasing the data in "SelectedBlock" before I want it to. What am I doing wrong?
UPDATE:
Thanks for the help everyone. I fixed it.
Block* selectedBlock = [[[Block alloc] init] retain];
This creates (and unnecessarily retains, since you already own it) a new Block. Why would you want to create a new one when your aim is to retrieve one that you already have?
// Move the block to the selected block
selectedBlock = tempBlock;
That comment doesn't make sense. Nothing is moved from one block to another; you are setting the selectedBlock variable to point to the block you got from the array. After that point, selectedBlock and tempBlock both point to the same block, which is the block in the array.
// Take the block out of the array
[buildBlocks removeObjectAtIndex:i];
The array owns all of the blocks it contains, so when you remove the block you got from the array, the array releases it. If that was the only ownership of that block, the block consequently gets deallocated. Any use of it thereafter is invalid.
Such as…
if( selectedBlock.selected ) // <---- Crashes here
selectedBlock points to the block you got and then removed from the array. Assuming the array was the only thing owning it, it is a dead object by this point, so yes, sending it a message causes a crash.
You retained the object that you initialized selectedBlock with, but not the object that you replaced that object with later. Retaining that initial object did not pre-actively retain any future objects you assigned to the variable; it retained only that initial object.
There are several things you need to change:
Initialize selectedBlock to nil, not a pointer to a new Block.
Don't retain things at random. Always retain with a purpose. If you don't fully understand why retaining something is the right thing to do (“makes it not crash” is not, by itself, an acceptable reason), don't just throw a retain on it. Understand the memory-management rules in the Advanced Memory Management Programming Guide and you'll know when you need to retain, and why your retain in [[[Block alloc] init] retain] is unnecessary.
When you retain something, always balance it out with a release or autorelease message. A retain that you don't balance out is a leak, and leaks cause problems eventually. Under iOS, they cause what is effectively, from the user's perspective, a crash (more accurately, you use too much memory and the system kills your app).
When you assign the object from the array to selectedBlock, retain it and autorelease it before you remove it from the array. The retain makes you an owner, and the autorelease makes that temporary; being an owner, for as long as that lasts, will keep the object alive long enough for you to use it, preventing the crash.
Don't bother asking the selected block whether it's selected. You only assign a Block's pointer to selectedBlock if it's selected, so by the time you go to use selectedBlock, you already know that it's selected. Combined with #1 above, you can simply test whether selectedBlock is nil; if it isn't nil, there is a selected block, and if it is nil, you did not find (i.e., there is not) a selected block.
After you get this code working, convert it to ARC. (There's a menu item for this in the Edit/Refactor menu.) Then you don't have to retain or release or autorelease anything; most things just work.
I would say that the block is being released from under you when you remove it from the array, but you've put in spurious (and unnecessary) retains in there so it's hard to tell what is going on without seeing the code that you are leaving out.
Normally, when I remove and object from an array and want to keep hold of it, I retain it. But you're over-retaining anyway so that might not be the problem, but without seeing the rest of the method, I can't be sure.
Here you go:
NSMutableArray* buildBlocks = [[[NSMutableArray alloc] initWithCapacity:0] retain];
Block* selectedBlock;
// Add several blocks to "buildBlocks"
for( int i=0; i < [buildBlocks count]; i++)
{
Block* tempBlock = [buildBlocks objectAtIndex:i];
if( tempBlock.selected )
{
// Move the block to the selected block
selectedBlock = tempBlock;
[selectedBlock retain]; // Retain selectedBlock here
// Take the block out of the array
[buildBlocks removeObjectAtIndex:i];
}
}
// Some code later
if( selectedBlock.selected ) // <---- Crashes here
{
// Do stuff
}
[selectedBlock release]; // release when done.
Basically, you were retaining a brand new, never used Block in line 2. selectedBlock never got retained and when you removed it from the array, it was destroyed. Thus selectedBlock was pointing to an old stale destroyed piece of memory causing a crash.

Objective-C Memory Management: crash on release

I'm new to Objective-C and can't seem to get the memory management code right. I have the following code:
Media* myMedia = [self.myMediaManager getNextMedia];
self.navigationItem.title = [self.myMediaManager getCategory];
[self.btnImage setImage:myMedia.imageFile forState: UIControlStateNormal];
[self.lblImage setText:myMedia.imageLabel];
//[myMedia release];
My app crashes if I uncomment the above line. Do I need to do something special when I instantiate myMedia?
EDIT:
If myMediaManager is supposed to release it, when would it do that. Here is my code for getNextMedia:
- (Media*) getNextMedia {
DLog(#"Start");
Media* nextMedia = [[Media alloc] init];
[self setNextMediaIndex];
if (self.mediaIndex > -1)
{
nextMedia = [mediaArray objectAtIndex: self.mediaIndex];
}
return nextMedia;
}
EDIT2: I fixed the crashing issue (I was releasing an object I didn't own). I still see leaks and can't seem to find what the issue is.
Only objects that you own can be released.
You can release objects if you new, alloc, copy, mutableCopy or retain them first.
Since there is no alloc/copy/retain in [self.myMediaManager getNextMedia]; you can't release it.
Since myMedia is not retained here, you don't need to release it. When the origin (self.myMediaManager) releases it, it gets destroyed immediately.
NSString *string = [[NSString alloc] init];
[string release]; // now we have to release the string, since we allocated it.
NSString *string = self.navigationItem.title;
// now we don't, since it's a property of `navigationItem` and we didn't retain it.
At this point, since you are just learning, you should probably just start off using ARC with the iOS5 beta versions of XCode. It's good to have an understanding but using ARC will save you many potential pitfalls - by the time you learn enough to produce something iOS5 will be out. You can still build applications targeting iOS4, so you'll still be able to reach a lot of people.
The general rule for memory management is as follows:
For every retain, alloc, copy, or new, you need to call release or autorelease.
Since you did not call any these, you do not need to release myMedia.
For more information, take a look at this other answer I posted that deals with the subject. Also, since you are new to iOS development, I suggest looking at this answer as well.
This updated code is suspicious:
Media* nextMedia = [[Media alloc] init];
[self setNextMediaIndex];
if (self.mediaIndex > -1)
{
nextMedia = [mediaArray objectAtIndex: self.mediaIndex];
}
Depending on the condition in the if() clause, you assign a new value to nextMedia, which makes the value you just allocated unreachable, i.e. it can't be released.
Also, you don't retain the value you get from the array, so you should not release it either. But if the if() clause does not run, you still have the instance you alloc-ed, and that should be released.
That is not good. Try:
Media* nextMedia = [[Media alloc] init];
[self setNextMediaIndex];
if (self.mediaIndex > -1)
{
[nextMedia release];
nextMedia = [[mediaArray objectAtIndex: self.mediaIndex] retain];
}
You could also do (and I would prefer that):
Media *nextMedia;
[self setNextMediaIndex];
if (self.mediaIndex > -1)
{
nextMedia = [[mediaArray objectAtIndex: self.mediaIndex] retain];
}
else
{
nextMedia = [[Media alloc] init];
}
Now you can release nextMedia when that is not needed anymore, without any ambiguity about the retain count.

iPhone: I am having trouble with memory iteratively building up

I am having trouble with memory building up and am not able to empty it once I am done with it. When I look at the diagnostic tool ": Allocations: Instruments: Object summary: statistics", the memory is just continuously building up.
example:
for (int i=0; i<100000; i++){
UILabel *lblPost = [[UILabel alloc] initWithFrame:CGRectMake(x,y,w,d)];
[lblPost setText: "Hello World"];
[self.view addSubview: lblPost];
// tried each of the following
//[lblPost dealloc]; //neither work to clear memory it just builds
//[lblPost release]; //
}
--> Do I need to seperate CGRect out and clear that.
--> (I know I can just keep writing to one label, this is a simplified version where in the bigger version, one label would not work so easily. )
--> (I find it hard to believe that I can not create an object and then destroy it 10000 or 100000000 times over. In standard C, I can accomplish this with memory-blocks by using "free()" )
The view you are adding your label to is retaining it, that's why each none of the labels is deallocated (even if you send release message)
Maybe i really don't understand what you're trying to do, but your each indiviual object you'r allocating is retained in the view. Let me try to explain it in the code:
for (int i=0; i<10000; i++){
UILabel *lblPost = [[UILabel alloc] initWithFrame:CGRectMake(x,y,w,d)];
// lblPost now has a retain count of 1, as you alloc'd it, you'll have to release it!
[lblPost setText: "Hello World"];
[self.view addSubview: lblPost];
// lblPost now has a retain count of 2, as adding to the view adds a reference to it
[lblPost release]
// you alloc'd it, now you should release it. it now has a retain count of 1, which means it's in the ownerhsip of the self.view
}
Now, when you release or free self.view, the lblPost objects should be released as well
Why are you allocating memory for 10000 UILabels and adding them as a subview exactly? That's iPhone torture. The items in bold cause your surge in memory. Plus you're releasing none of them.
Also - never ever ever call dealloc yourself - dealloc is called automatically when you release something.
This is fun! I have decided to jump in. I posted this in my comments but here it is again:
I think madhu misunderstood the [lblPost release]. This "release" only applies to the lblPost instance. Not the ones retained by self.view... etc. So you still have 10000 label retained by self.view...
So, you create 10000 instances of lblPost and then release all of them (10000) by this line [lblPost release] in your for loop. That is just fine. But then in your for loop you also have this line [self.view addSubview: lblPost]. This line will add 10000 instances to your self.view. And they are the reason why your system crashed.
for (int i=0; i<10000; i++){
UILabel *lblPost = [[UILabel alloc] initWithFrame:CGRectMake(x,y,w,d)];
[lblPost setText: "Hello World"];
[self.view addSubview: lblPost];
[lblPost release];
//You should release after adding it to your view
}

How to release an object in a forin loop?

I'm new to cocoa / objective-c and i'm struggeling with the releases of my objects. I have the following code:
gastroCategoryList = [[NSMutableArray alloc] init];
for (NSDictionary *gastrocategory in gastrocategories) {
NSString *oid = [gastrocategory objectForKey:#"id"];
GastroCategory *gc = [[GastroCategory alloc] initWithId:[oid intValue] name:[gastrocategory objectForKey:#"name"]];
[gastroCategoryList addObject:gc];
}
The analyzer shows me that the "gastrocategory" defined in the for is a potential memory leak. But i'm not sure if i can release this at the end of the for loop?
Also at the following code:
- (NSArray *)eventsForStage:(int)stageId {
NSMutableArray *result = [[NSMutableArray alloc] init];
for (Event *e in eventList) {
if ([e stageId] == stageId) {
[result addObject:e];
}
}
return result;
}
The Analyzer tells me that my "result" is a potential leak. But where should I release this?
Is there also a simple rule to memorize when i should use assign, copy, retain etc. at the #property ?
Another problem:
- (IBAction)showHungryView:(id)sender {
GastroCategoriesView *gastroCategoriesView = [[GastroCategoriesView alloc] initWithNibName:#"GastroCategoriesView" bundle:nil];
[gastroCategoriesView setDataManager:dataManager];
UIView *currentView = [self view];
UIView *window = [currentView superview];
UIView *gastroView = [gastroCategoriesView view];
[window addSubview:gastroView];
CGRect pageFrame = currentView.frame;
CGFloat pageWidth = pageFrame.size.width;
gastroView.frame = CGRectOffset(pageFrame,pageWidth,0);
[UIView beginAnimations:nil context:NULL];
currentView.frame = CGRectOffset(pageFrame,-pageWidth,0);
gastroView.frame = pageFrame;
[UIView commitAnimations];
//[gastroCategoriesView release];
}
I don't get it, the "gastroCategoriesView" is a potential leak. I tried to release it at the end or with autorelease but neither works fine. Everytime I call the method my app is terminating. Thank you very much again!
In your loop, release each gc after adding it to the list since you won't need it in your loop scope anymore:
gastroCategoryList = [[NSMutableArray alloc] init];
for (NSDictionary *gastrocategory in gastrocategories) {
NSString *oid = [gastrocategory objectForKey:#"id"];
GastroCategory *gc = [[GastroCategory alloc] initWithId:[oid intValue] name:[gastrocategory objectForKey:#"name"]];
[gastroCategoryList addObject:gc];
[gc release];
}
In your method, declare result to be autoreleased to absolve ownership of it from your method:
NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease];
// An alternative to the above, produces an empty autoreleased array
NSMutableArray *result = [NSMutableArray array];
EDIT: in your third issue, you can't release your view controller because its view is being used by the window. Setting it to autorelease also causes the same fate, only delayed.
You'll have to retain your GastroCategoriesView controller somewhere, e.g. in an instance variable of your app delegate.
BoltClock's answer is spot-on as to the first part of your question. I'll try to tackle the rest.
Assign is for simple, non-object types such as int, double, or struct. It generates a setter that does a plain old assignment, as in "foo = newFoo". Copy & retain will, as their names imply, either make a copy of the new value ("foo = [newFoo copy]") or retain it ("foo = [newFoo retain]"). In both cases, the setter will release the old value as appropriate.
So the question is, when to copy and when to retain. The answer is... it depends. How does your class use the new value? Will your class break if some other code modifies the incoming object? Say, for example, you have an NSString* property imaginatively named "theString." Other code can assign an NSMutableString instance to theString - that's legal, because it's an NSString subclass. But that other code might also keep its own reference to the mutable string object, and change its value - is your code prepared to deal with that possibility? If not, it should make its own copy, which the other code can't change.
On the other hand, if your own code makes no assumptions about whether theString might have been changed, and works just as well whether or not it was, then you'd save memory by retaining the incoming object instead of unnecessarily making a copy of it.
Basically, the rule, which is unfortunately not so simple sometimes, is to think carefully about whether your own code needs its own private copy, or can correctly deal with a shared object whose value might be changed by other code.
The reason you can release gc after it is added to the gastroCategoryList is that when an object is added to an array, the array retains that object. So, even though you release your gc, it will still be around; retained by the gastroCategoryList.
When you are returning a newly created object from a method, you need to call autorelease. This will cause the object to be released only after the runtime leaves the scope of the calling method, thereby giving the calling method a chance to do something with the returned value.
Note that if your method starts with the word copy or new, then you should not autorelease your object; you should leave it for the calling method to release.
As for copy vs retain vs assign... as a general rule, copy objects that have a mutable version, such as NSArray, NSSet, NSDictionary, and NSString. This will ensure that the object you have a pointer to is not mutable when you don't want it to be.
Otherwise, use retain whenever you want your class to be ensured that an object is still in memory. This will apply to almost every object except for objects that are considered parents of your object, in which case you would use assign. (See the section on retain cycles here).
Also note that you have to use assign for non-object types such as int.
Read through the Memory Management Programming Guide a bit; it's quite helpful.

Instruments Living Count and/or Retain Count Issue

I'm running into a retain count issue that I do not understand. (I've added what I believe to be the retain count of vertex in [] at the end of each line of code).
CBVertex *vertex = nil;
for(int i=0; i<10; i++) {
vertex = [[CBVertex alloc] initWithFrame:CGRectMake(minX, y, 10.0, 10.0)]; // retain count [1]
[vertex setTag:i];
[vertex setAnimationDelegate:self];
[gameboard addSubview:vertex]; // retain count [2]
[tripGraph addVertex:vertex]; // retain count [3]
[vertex release]; vertex=nil; // retain count [2]
}
CBVertex is a subclass of UIView, gameboard is a UIView and tripGraph is a class that, among other things, has an NSMutableArray (privateVerticies) to which vertex is added to in its addVertex method.
After the above is executed, Instruments shows that there are 10 instances of CBVertex living.
Later in the code execution (I've confirmed that this code executes):
[[tripGraph verticies] makeObjectsPerformSelector:#selector(removeFromSuperview)];
// gameboard should have no references to any of the CBVertex's (correct??)
[tripGraph removeAllVerticies];
// tripGraph privateVerticies is empty and no references to any of
// the CBVertex's (correct?)
Relevant tripGraph methods:
-(NSArray *) verticies {
return [NSArray arrayWithArray:privateVerticies];
}
-(void) tripGraph removeAllVerticies {
[privateVerticies removeAllObjects];
}
- (void) addVertex:(CBVertex *)vertex {
[privateVerticies addObject:vertex];
}
The issue arises when the second set of CBVertex's are created. Instruments shows that the first set of CBVertex's is still live (i.e. the number of instances of CBVertexs is now 20).
I'm (obviously?) missing a release somewhere, but don't understand where . . .
Help/pointers are appreciated!!
thanks
tom
If gameboard and tripGraph are still retaining that CBVertex object, then even if you release it in your loop, it will continue to exist until you remove the CBVertex object from gameboard and tripGraph as well.
OK, I'm closing this out as the code above is correct. The problem lies in my overriden removeFromSuperview method in which there is an animation going on. I'm going to investigate further and if I don't have it figured out, I'll repost a new question. (and link to it from here).
Thanks for the comments, answer and several views.
For those interested, here's what was going on and how it was resolved.
In CBVertex, I've overridden removeFromSuperview. In that overridden method, I am animating the view's layer and setting the view as the CAAnimations delegate (which is retained by the CAAnimation) and calling super removeFromSuperview. The animation is not removed on completion.
As the animation retains the delegate and the animation is not removed, the view's retain count remains at +1.
My resolution was to create an intermediate method to perform the animation. When the animation is complete it calls the overriden removeFromSuperview, which now only removes all animations and calls super. Removing the animation releases it, which in turn releases it's reference to it's delegate (the CBVertex) and the CBVertex's retain count goes to +0.
One final thought for anyone chasing retain counts: don't think of them as absolute values. The inner workings of the objects you may be using might be retaining your instances more than you'd expect -- think of retain counts as a delta.
SomeClass *myObject = [[SomeClass alloc] init]; // retain count +1
[someMutableSet addObject:myObject]; // retain count +2