PDA

View Full Version : Memory Management In Cocoa




Jordan72
Dec 30, 2005, 02:11 PM
I'm having some difficulty with the memory management with Cocoa. The best way I can explain the situation is by giving an analogy, because my real example would be harder to explain.

I have a Car Class. Inside this Car Class, there is an instance of an engine object from an Engine Class. The Engine Class is composed of objects, which make up the engine. From time to time, the car object is taken into the garage and a few parts for the engine need to be replaced, but not the whole engine. The way these parts are maintained in Objective-C lingo is that old parts are deallocated and replacement parts are allocated.

I know during the program execution that maintenance will occur several times. I have no idea which parts will be replaced each time maintenance is needed, so memory allocation and deallocation has to be flexible. I can't just remove a certain list of parts. And it makes no sense to replace the engine every time. I only want to replace the parts that need to be replaced, which means I need to deallocate them and create new object versions of them for my Engine instance.

The engine is in an NSAutoreleasePool instance. The only way I can replace bad parts is to release the pool to which all engine parts belong to. If I retain the parts that don't need to be replaced and then release the pool, it would work. But I would then have a new problem. Now the other parts are just floating around and I can't replace them by deallocating them, unless I create a new pool, then release that pool. But I'm not sure if this is the best way to handle it.

I would just send a free message to replacement parts and simply allocated new ones, but Cocoa forces some of my objects into the pool that is currently on the top of the stack, making a situation more complex than just simple freeing them with a message.

I need some input on the best way to deal with this.



dxm113
Dec 30, 2005, 02:55 PM
I can't really offer too much help, but you might want to join the Cocoa ADC mailing list (http://lists.apple.com/mailman/listinfo/cocoa-dev) and ask your question there.

Jordan72
Dec 30, 2005, 05:16 PM
I can't really offer too much help, but you might want to join the Cocoa ADC mailing list (http://lists.apple.com/mailman/listinfo/cocoa-dev) and ask your question there.

Interesting. I checked it out. I didn't know Apple had a development forum under the name: Mailing List. Well, who would of known?

I'll have to see how things go there. I've signed up.

HiRez
Dec 30, 2005, 06:11 PM
Why do you need to destroy an autorelease pool containing everything to replace objects? Hard to understand exactly what it is you're trying to do... :confused:

Catfish_Man
Dec 30, 2005, 07:27 PM
It sounds like you really don't understand retain and release. free() should never be used in Cocoa. Whenever an object gains ownership of another it should -retain it, and whenever it is deallocated it should -release any objects that it owns. When an object is replaced, you -autorelease the current value, and -retain the new value, like this:

- (void) setFoo:(Foo *)bar
{
[foo autorelease];
foo = [bar retain];
}

I could just be misinterpreting what you're saying though.

savar
Dec 30, 2005, 08:29 PM
The engine is in an NSAutoreleasePool instance. The only way I can replace bad parts is to release the pool to which all engine parts belong to. If I retain the parts that don't need to be replaced and then release the pool, it would work. But I would then have a new problem. Now the other parts are just floating around and I can't replace them by deallocating them, unless I create a new pool, then release that pool. But I'm not sure if this is the best way to handle it.

I would just send a free message to replacement parts and simply allocated new ones, but Cocoa forces some of my objects into the pool that is currently on the top of the stack, making a situation more complex than just simple freeing them with a message.

First off, like somebody else already said, you never use free unless you used malloc (or a sibling) first. Free doesn't do anything in cocoa.

Okay, now to your question. Why is engine an autorelease pool? Is engine actually supposed to represent an engine, or is that just part of your analogy? Because a data model should never be an autorelease pool.

If you were just using the engine as a metaphor, then I think you're using autorelease pools in the wrong way. The only reason to use an autorelease pool is that you have objects you don't want anymore, but you don't want to release them right away because if you do that might destroy them. So you would put it in an autorelease pool to keep them alive long enough to allow other objects a chance to retain them. Then you release the autorelease pool and it releases all your objects. But don't use autorelease just to group related objects. If you do that, then you get exactly the problem that I think you're describing above: throwing out the baby with the bathwater.

Instead, you need to have a well-formed object tree. Your controller class should have a pointer to the engine, and nothing else. Don't have a pointer to the manifold, or pointers to the valves. The engine should have pointers to all of the objects it logically "contains", and each of those objects in turn contains pointers to the objects it contains. So engine contains valves and cams, cams contain camrods and cam heads, heads contain metal alloy and geometry, etc. But engine does not have a pointer to the metal alloy of the cam head.

Then if you want to replace an object, say the carburetor, you say [[myEngine getCarburetor] release]. If your release message sends the carburetor's reference count to zero, then cocoa will call its dealloc method and that method should in turn release anything that the carburetor contains, and each of those pieces release the pieces they contains, etc.

So just follow the rules: Anything alloc'ed is automatically retained. Factory methods return autoreleased objects, so if you want to keep them send them a retain. And then release any objects which you explicitly retain.

I don't usually followup in threads that aren't first page, so if you have any questions email me at mehaase at gmail dot com

kainjow
Dec 31, 2005, 11:46 AM
I think you're optimizing way to earlier again.

If you're developing a Cocoa app, the only place NSAutoreleasePool should be used (unless you 110% know what you're doing), is in methods that are threaded.

For all others, the pool is created for you automatically if you use NSApplicationMain() from your main() function.

The only other place you might setup your own pool is if you're doing a huge loop and don't want all the objects to get piled up into the main autorelease pool, so you create another pool which gets freed more often. But I've rarely seen anyone do this. You'd really have to test it to see if it benefits.

Jordan72
Dec 31, 2005, 05:58 PM
Thanks for all the input.

Maybe an example will be better to work with. I've had a few issues solved since my first post, but this is where I am.

Any positive or negative is appreciated. Both will help.


#import <Foundation/Foundation.h>

//I found a simple jet engine image with parts labeled, so I had something to go by. The link follows.

//http://images.google.com/imgres?imgurl=http://virtualskies.arc.nasa.gov/aeronautics/tutorial/images/TurbEngine.gif&imgrefurl=http://virtualskies.arc.nasa.gov/aeronautics/tutorial/structure.html&h=154&w=375&sz=5&tbnid=y-fmuMR-BWoJ:&tbnh=48&tbnw=118&hl=en&start=17&prev=/images%3Fq%3Dengine%2Bparts%2Btutorial%26svnum%3D100%26hl%3Den%26lr%3Dlang_en%26client%3Dsafari%26rl s%3Den%26sa%3DG

//These are the jet engine parts in the diagram, which are used as filler objects for the Engine Class.
@interface CombustionChamber : NSObject{} @end
@implementation CombustionChamber @end

@interface Compressor : NSObject{} @end
@implementation Compressor @end

@interface FuelInjector : NSObject{} @end
@implementation FuelInjector @end

@interface Shaft : NSObject{} @end
@implementation Shaft @end

@interface Turbine : NSObject{} @end
@implementation Turbine @end

@interface Engine : NSObject
{
NSMutableArray *engineParts;
}

- (id)init;
- (void)maintenance:(NSArray*)badParts;

@end


@implementation Engine

- (id)init;
{
[super init];

engineParts = [[NSMutableArray alloc] init];

//Here are the parts for this engine. I have added two Fuel Injectors.
[engineParts addObject:[[CombustionChamber alloc] init]];
[engineParts addObject:[[Compressor alloc] init]];
[engineParts addObject:[[FuelInjector alloc] init]];
[engineParts addObject:[[FuelInjector alloc] init]];
[engineParts addObject:[[Shaft alloc] init]];
[engineParts addObject:[[Turbine alloc] init]];

return self;
}

- (void)dealloc;
{
[engineParts release];

[super dealloc];
}

- (void)maintenance:(NSArray*)badParts
{
int i;

id PartClass;//This is used to dynamically choose the kind of class that will be used to allocate engine parts.


NSLog(@"Parts before maintenance:\n");
for(i=0; i<[engineParts count]; i++)
NSLog(@"Part%i: %@", i, [[engineParts objectAtIndex:i] description]);

//Remove and replace bad parts.
for( i=0; i<[badParts count]; i++ )
{
PartClass = [[engineParts objectAtIndex:[[badParts objectAtIndex:i] intValue]] class];

[engineParts removeObjectAtIndex: [[badParts objectAtIndex:i] intValue]];
[engineParts insertObject:[[PartClass alloc] init] atIndex:[[badParts objectAtIndex:i] intValue]];
}

NSLog(@"Parts after maintenance:\n");
for(i=0; i<[engineParts count]; i++)
NSLog(@"Part%i: %@", i, [[engineParts objectAtIndex:i] description]);

printf("\n");
}

@end

int main (int argc, const char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

Engine *myEngine = [[Engine alloc] init];

//This is the bad parts list for the first maintenance. The parts to be replaced are the first two.
NSArray *badParts1 = [[NSArray alloc] initWithObjects: [[NSNumber alloc] initWithInt:0], [[NSNumber alloc] initWithInt:1], nil ];
//This list causes the last part to be removed.
NSArray *badParts2 = [[NSArray alloc] initWithObjects: [[NSNumber alloc] initWithInt:5], nil ];

[myEngine maintenance:badParts1];
[myEngine maintenance:badParts2];

[badParts1 release];
[badParts2 release];
[myEngine release];
[pool release];

return 0;
}

kainjow
Dec 31, 2005, 06:25 PM
First, whenever you're adding objects to an array or dictionary, the array or dictionary retains the object. So you need to make sure it is autoreleased or you need to make sure you release it after you add it.

For example, you do this:

//Here are the parts for this engine. I have added two Fuel Injectors.
[engineParts addObject:[[CombustionChamber alloc] init]];
[engineParts addObject:[[Compressor alloc] init]];
[engineParts addObject:[[FuelInjector alloc] init]];
[engineParts addObject:[[FuelInjector alloc] init]];
[engineParts addObject:[[Shaft alloc] init]];
[engineParts addObject:[[Turbine alloc] init]];
You're leaking memory here. Each of these lines should be like this:
[engineParts addObject:[[[Shaft alloc] init] autorelease]];

Second, instead of removing an object, and then inserting another, you can simply replace:
//Remove and replace bad parts.
for( i=0; i<[badParts count]; i++ )
{
PartClass = [[engineParts objectAtIndex:[[badParts objectAtIndex:i] intValue]] class];

[engineParts replaceObjectAtIndex:[[badParts objectAtIndex:i] intValue] withObject:[[[PartClass alloc] init] autorelease]];
}

Again, here you must autorelease these objects since you aren't releasing them later (if you declared them as separate variables before adding them to the array, you could release them later. I went ahead and used NSNumber's convenient class methods which returns an autoreleased object. And again you can use NSArray's class method which returns an autorelease object also):

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Engine *myEngine = [[Engine alloc] init];

NSArray *badParts1 = [NSArray arrayWithObjects:[NSNumber numberWithInt:0], [NSNumber numberWithInt:1], nil];
NSArray *badParts2 = [NSArray arrayWithObject:[NSNumber numberWthInt:5]];

[myEngine maintenance:badParts1];
[myEngine maintenance:badParts2];

[myEngine release];
[pool release];

Jordan72
Dec 31, 2005, 08:10 PM
All of your advice was very useful and things have been changed. Although, there is one thing I didn't make clear. It has to do with this comment:


You're leaking memory here. Each of these lines should be like this:
[engineParts addObject:[[[Shaft alloc] init] autorelease]];


I really need the -maintenance method to cause the old parts to be deallocated right away. Why? I have to keep things open and flexible. I may need to in the future. I'm assuming there may be so many parts that I cannot have bad parts and the parts that will replace those parts existing at one time, otherwise I may get paging. If I put them for later release, that is what could happen. So I want to be clean right now about it.

I attempted to handle this by adding the enginePartsPtr to the Engine field data. I'm not sure if I over complicated things, but if I did, please give me the better way out.

#import <Foundation/Foundation.h>

//Filler objects.
@interface CombustionChamber : NSObject{} @end
@implementation CombustionChamber @end

@interface Compressor : NSObject{} @end
@implementation Compressor @end

@interface FuelInjector : NSObject{} @end
@implementation FuelInjector @end

@interface Shaft : NSObject{} @end
@implementation Shaft @end

@interface Turbine : NSObject{} @end
@implementation Turbine @end

@interface Engine : NSObject
{
NSMutableArray *engineParts;
id *enginePartsPtr;
}

- (id)init;
- (void)maintenance:(NSArray*)badParts;

@end


@implementation Engine

- (id)init;
{
int i;

[super init];

engineParts = [[NSMutableArray alloc] init];

[engineParts addObject:[[CombustionChamber alloc] init]];
[engineParts addObject:[[Compressor alloc] init]];
[engineParts addObject:[[FuelInjector alloc] init]];
[engineParts addObject:[[FuelInjector alloc] init]];
[engineParts addObject:[[Shaft alloc] init]];
[engineParts addObject:[[Turbine alloc] init]];

enginePartsPtr = (id *)(malloc(sizeof(id)*[engineParts count]));

for( i=0; i<[engineParts count]; i++ )
enginePartsPtr[i] = [engineParts objectAtIndex:i];

return self;
}

- (void)dealloc;
{
[engineParts release];

[super dealloc];
}

- (void)maintenance:(NSArray*)badParts
{
int i, part;

id PartClass;

NSLog(@"Parts before maintenance:\n");
for(i=0; i<[engineParts count]; i++)
NSLog(@"Part%i: %@", i, [[engineParts objectAtIndex:i] description]);

for( i=0; i<[badParts count]; i++ )
{
part = [[badParts objectAtIndex:i] intValue];

PartClass = [[engineParts objectAtIndex:part] class];

[enginePartsPtr[part] release];

[engineParts replaceObjectAtIndex:[[badParts objectAtIndex:i] intValue] withObject:[[PartClass alloc] init]];

enginePartsPtr[part] = [engineParts objectAtIndex:part];
}

NSLog(@"Parts after maintenance:\n");
for(i=0; i<[engineParts count]; i++)
NSLog(@"Part%i: %@", i, [[engineParts objectAtIndex:i] description]);

printf("\n");
}

@end

int main (int argc, const char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

Engine *myEngine = [[[Engine alloc] init] autorelease];

NSArray *badParts1 = [NSArray arrayWithObjects: [NSNumber numberWithInt:0], [NSNumber numberWithInt:1], nil ];

NSArray *badParts2 = [NSArray arrayWithObjects: [NSNumber numberWithInt:5], nil ];

[myEngine maintenance:badParts1];
[myEngine maintenance:badParts2];

[pool release];

return 0;
}

HiRez
Jan 1, 2006, 05:20 AM
I really need the -maintenance method to cause the old parts to be deallocated right away. Why? I have to keep things open and flexible. I may need to in the future. I'm assuming there may be so many parts that I cannot have bad parts and the parts that will replace those parts existing at one time, otherwise I may get paging. If I put them for later release, that is what could happen. So I want to be clean right now about it.In that case, use -release instead of -autoreleaseShaft *aShaft = [[Shaft alloc] init];
[engineParts addObject:aShaft];
[aShaft release];Once you add an object to a collection, it's retained by the collection, so you can release it immediately.

Jordan72
Jan 1, 2006, 10:38 PM
I can't really offer too much help, but you might want to join the Cocoa ADC mailing list (http://lists.apple.com/mailman/listinfo/cocoa-dev) and ask your question there.

Wow! That really was a good idea. Look how nice this looks now compared to what is was then. I was swamped with suggestions. I barely could keep up. I learned alot in the last few days with this example.

#import <Foundation/Foundation.h>

//EnginePart Class
@interface EnginePart : NSObject
{
NSDate *dateCreated;
}
-(id)init;
-(NSDate*)creationDate;
@end
@implementation EnginePart
- (id)init
{
self = [super init];
if (self != nil)
{
dateCreated = [[NSDate date] retain];
}
return self;
}
- (void)dealloc;
{
[dateCreated release];
[super dealloc];
}
- (NSDate*)creationDate
{
return dateCreated;
}
- (NSString*)description
{
return [self className];
}
@end

//Individual Sub-Classes of EnginePart Class
@interface CombustionChamber : EnginePart{} @end
@implementation CombustionChamber @end

@interface Compressor : EnginePart{} @end
@implementation Compressor @end

@interface FuelInjector : EnginePart{} @end
@implementation FuelInjector @end

@interface Shaft : EnginePart{} @end
@implementation Shaft @end

@interface Turbine : EnginePart{} @end
@implementation Turbine @end


@interface Engine : NSObject
{
NSMutableArray *enginePartArray;
}

- (id)init;
- (void)maintenance:(NSMutableIndexSet*)badParts;

@end


@implementation Engine

- (id)init
{
self = [super init];
if (self != nil)
{
int i;
enginePartArray = [[NSMutableArray alloc] init];

[enginePartArray addObject:[[CombustionChamber alloc] init]];
[enginePartArray addObject:[[Compressor alloc] init]];
[enginePartArray addObject:[[FuelInjector alloc] init]];
[enginePartArray addObject:[[FuelInjector alloc] init]];
[enginePartArray addObject:[[Shaft alloc] init]];
[enginePartArray addObject:[[Turbine alloc] init]];

[enginePartArray makeObjectsPerformSelector:@selector(release)];
}
return self;
}

- (void)dealloc;
{
[enginePartArray release];

[super dealloc];
}

- (void)maintenance:(NSMutableIndexSet*)badParts
{
int i, part, badPartCount;

id PartClass;

NSLog(@"Parts before maintenance:\n");
NSEnumerator *enumerator = [enginePartArray objectEnumerator];
id aPart;
while (aPart = [enumerator nextObject])
NSLog(@"Part: %@ created: %@", aPart, [[aPart creationDate] descriptionWithCalendarFormat:@"%S" timeZone:nil locale:nil]);


part = [badParts firstIndex];

while (part != NSNotFound)
{
PartClass = [[enginePartArray objectAtIndex:part] class];

[enginePartArray replaceObjectAtIndex:part withObject:[[PartClass alloc] init]];

[[enginePartArray objectAtIndex:part] release];

part = [badParts indexGreaterThanIndex:part];
}

NSLog(@"Parts after maintenance:\n");
enumerator = [enginePartArray objectEnumerator];
while (aPart = [enumerator nextObject])
NSLog(@"Part: %@ created: %@", aPart, [[aPart creationDate] descriptionWithCalendarFormat:@"%S" timeZone:nil locale:nil]);

printf("\n");
}

@end

int main (int argc, const char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

Engine *myEngine = [[[Engine alloc] init] autorelease];

NSMutableIndexSet *badPartsIndexSet1 = [[NSMutableIndexSet alloc] init];
NSMutableIndexSet *badPartsIndexSet2 = [[NSMutableIndexSet alloc] init];

[badPartsIndexSet1 addIndex:0];
[badPartsIndexSet1 addIndex:1];
[badPartsIndexSet2 addIndex:5];

[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];

[myEngine maintenance:badPartsIndexSet1];

[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];

[myEngine maintenance:badPartsIndexSet2];

[badPartsIndexSet1 release];
[badPartsIndexSet2 release];

[pool release];

return 0;
}