Re: Managed Object won't dealloc even after Hit with Kitchen Sink
Re: Managed Object won't dealloc even after Hit with Kitchen Sink
- Subject: Re: Managed Object won't dealloc even after Hit with Kitchen Sink
- From: Quincey Morris <email@hidden>
- Date: Sun, 21 Dec 2008 22:26:21 -0800
(Comments sprinkled through your long post)
On Dec 21, 2008, at 21:17, Jerry Krinock wrote:
REAL-LIFE PROBLEM
In a managed memory application, I have a managed object which,
besides its Core Data managed properties, has a single instance
variable, a worker-kind of object which does some heavy lifting for
it. So that this retained worker, etc., will be deallocced, I need
this managed object to get deallocced when no longer needed.
The documentation [1] seems very clear that, although the
'insert...' methods return an autoreleased NSManagedObject, a
managed object may be retained by:
(a) the managed object context, until changes are saved
or rolled back, or,
(b) the managed object context's undo manager, as long as
an action involving the managed object remains on
the undo stack.
I conclude that if I save or roll back the moc, and reset the undo
stack, my managed object should be deallocced with the next
autorelease.
It seems to me that this is a fundamental misunderstanding of the
memory management contract. In general, you don't get to decide when
an object "should" be deallocated, because you don't control retains
of the object other than your own. There's nothing intrinsically wrong
with something in Core Data or elsewhere retaining your object even if
*you* have no further use for it.
The only control you have in general is to *prevent* an object from
being deallocated, not to *force* an object to be deallocated. (This
is not quite the whole story -- an autorelease pool managed inside a
tight loop kind of relies on your ability to get objects deallocated
when you want them to be, but even there you can't force deallocation
if something wants to hold onto the object.)
But despite my repeatedly doing so, these managed objects never
dealloc.
STUDY - DEMO APP
To investigate, I modified Apple's Low-Level Core Data Tutorial into
The World's Simplest Core Data Tool [3] which simply does this:
1. Create a Core Data in-memory stack
2. Disable undo registration
3. Insert a managed object Foo
4. Roll back the moc
5. Save the moc
6. Remove all actions from undo manager
7. Reset the moc
Redundant, I know, but despite my so throwing the kitchen sink at
it, when I then
8. Release the autorelease pool
and expect that my Foo should log a dealloc message, it doesn't
happen.
Removing some of the redundant steps does not help. It does dealloc
(with no crash) if I send it TWO release messages.
?????
What am I not understanding? Eric Wing was wondering the same
thing, never got a reply [2].
Thanks,
Jerry Krinock
[1] http://developer.apple.com/DOCUMENTATION/Cocoa/Conceptual/CoreData/Articles/cdMemory.html#/
/apple_ref/doc/uid/TP40001860
[2] http://www.cocoabuilder.com/archive/message/cocoa/
2005/9/27/147040
[3] The World's Simplest Core Data Tool
// To see the action, scroll down to main(),
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@interface Foo : NSManagedObject {
}
@end
@implementation Foo
// This is just to log init and dealloc.
- (id)initWithEntity:(NSEntityDescription*)entity
insertIntoManagedObjectContext:(NSManagedObjectContext*)moc {
self = [super initWithEntity:entity
insertIntoManagedObjectContext:moc] ;
NSLog(@"Initted %@", self) ;
return self ;
}
- (void)dealloc {
NSLog(@"Dealloccing %@", self) ;
[super dealloc] ;
}
@end
// Functions to create Core Data Stack
NSManagedObjectModel *managedObjectModel() {
static NSManagedObjectModel *mom = nil;
if (mom != nil) {
return mom;
}
mom = [[NSManagedObjectModel alloc] init];
NSEntityDescription *runEntity = [[NSEntityDescription alloc]
init];
[runEntity setName:@"Foo"];
[runEntity setManagedObjectClassName:@"Foo"];
[mom setEntities:[NSArray arrayWithObject:runEntity]];
return mom;
}
NSManagedObjectContext *managedObjectContext() {
static NSManagedObjectContext *moc = nil;
if (moc != nil) {
return moc;
}
moc = [[NSManagedObjectContext alloc] init];
NSPersistentStoreCoordinator *coordinator =
[[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel: managedObjectModel()];
[moc setPersistentStoreCoordinator: coordinator];
NSError *error;
NSPersistentStore *newStore ;
newStore = [coordinator
addPersistentStoreWithType:NSInMemoryStoreType
configuration:nil
URL:nil
options:nil
error:&error];
if (newStore == nil) {
NSLog(@"Store Configuration Failure\n%@",
([error localizedDescription] != nil) ?
[error localizedDescription] : @"Unknown Error");
}
return moc;
}
// The action...
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Get Core Data stack and stuff
NSManagedObjectModel *mom = managedObjectModel();
NSManagedObjectContext *moc = managedObjectContext();
NSUndoManager* undoManager = [moc undoManager] ;
NSEntityDescription *runEntity = [[mom entitiesByName]
objectForKey:@"Foo"];
// Insert a Foo without telling the undo manager
[undoManager disableUndoRegistration] ;
Foo *foo = [[Foo alloc] initWithEntity:runEntity
insertIntoManagedObjectContext:moc];
So one of the retains is your own -- you created the object, and
according to the rules, you must release it.
[moc rollback] ;
// In case rollback was not enough to make that moc give up
// my foo, save the moc
NSError *error = nil;
if (![managedObjectContext() save: &error]) {
NSLog(@"Error while saving\n%@",
([error localizedDescription] != nil)
? [error localizedDescription] : @"Unknown Error");
exit(1);
}
// As a further attempt to make sure that the undo manager
// is not holding a reference to our Foo,
[undoManager removeAllActions] ;
// Now, for a final nail in foo's coffin...
[moc reset] ;
// Now, NOBODY has any reason to hold on to foo, so
// when we release the pool, we expect that Foo should
// log a dealloc message:
[pool release];
// Actual result: no dealloc
// Experiment: If I send foo ^two^ release messages:
// [foo release] ;
// [foo release] ;
As noted above, one of the retains is your own. I don't know where the
other one came from, but it's possible that if you call
processPendingChanges on the moc the object will get released. Or the
object might be being cached somewhere, and there might be nothing you
can do about it.
Keep in mind that you're basically trying to track a retain count by
this experiment. As has been said in about 3 recent threads, "that way
madness lies."
// Then it will dealloc, with no crash.
// A third release message,
// [foo release] ;
// will cause a crash.
NSLog(@"Terminating") ;
return 0;
}
The garbage collection design pattern has taught us that we need to
separate the concept of "this object is not in use any more" (or "I've
stopped using this object") from the concept of deallocation. If an
object has an instance variable that represents a resource that needs
to be cleaned up, in a GC app you usually must find a way to tell the
owning object to do that clean up *before* you get to the finalize
method (which is the nearest equivalent to deallocate under GC).
That pattern is correct for retain/release apps too. It sometimes
works to do resource cleanup in deallocate, but there are cases (and
this may be one) where it's not the right thing to do.
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden