(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 (Cocoa-dev@lists.apple.com)

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:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to