Calling processPendingChanges from awakeFromInsert

  • Hello list,

    I'm having a strange problem while trying to set a value for an
    NSManagedObject in its "awakeFromInsert" method.  The code is as
    follows:

    - (void)awakeFromInsert
    {
        [super awakeFromInsert];

    NSManagedObjectContext* moc = [self managedObjectContext];

    // don't allow this new object to be undone
    [moc processPendingChanges];

    [[moc undoManager] disableUndoRegistration];

    [self performSelector:@selector(setUniqueProject) withObject:nil
    afterDelay:0];

    // turn the undo manager back on
    [moc processPendingChanges];
    [[moc undoManager] enableUndoRegistration];
    }

    The object is inserted by pressing a button binded to an
    NSArrayController who's set is binded to an NSTableView.  The problem
    is that when I run this code as is, it creates two identical objects
    in the table view.  If I comment out the two [moc
    processPendingChanges] lines, the problem goes away but then (of
    course) I can undo the actions in "setUniqueProject" and that is not
    good.

    Maybe there is an easier way to go about this entirely (I just want
    to set the "Project" relationship of this object to the current
    project when it is created ... can this really be that complicated?)

    Thanks!
    Mike
  • Until the awakeFromInsert is finished, the object is "incomplete"
    Calling processPendingChanges flushes (effectively commits) this
    incomplete object to the DB (thats a bad thing)
    Then, after awakeFromInsert, the real object is inserted, which
    appears as a dupe.

    You must not call processPendingChanges in awakeFromInsert or
    awakeFromFetch.
    For the same reasons, you can't call executeFecthRequest (which calls
    processPendingChanges), in either of the same awake methods.

    Under various conditions, you'll get two objects - and have really
    strange undo problems (even if you don't call the undo registration
    code), which I suspect you're having.

    Call your undo registration code after the object is inserted into
    the context.

    On Sep 20, 2007, at 4:13 PM, Michael Burns wrote:

    > Hello list,
    >
    > I'm having a strange problem while trying to set a value for an
    > NSManagedObject in its "awakeFromInsert" method.  The code is as
    > follows:
    >
    > - (void)awakeFromInsert
    > {
    > [super awakeFromInsert];
    >
    > NSManagedObjectContext* moc = [self managedObjectContext];
    >
    > // don't allow this new object to be undone
    > [moc processPendingChanges];
    >
    > [[moc undoManager] disableUndoRegistration];
    >
    > [self performSelector:@selector(setUniqueProject) withObject:nil
    > afterDelay:0];
    >
    > // turn the undo manager back on
    > [moc processPendingChanges];
    > [[moc undoManager] enableUndoRegistration];
    > }
    >
    > The object is inserted by pressing a button binded to an
    > NSArrayController who's set is binded to an NSTableView.  The
    > problem is that when I run this code as is, it creates two
    > identical objects in the table view.  If I comment out the two [moc
    > processPendingChanges] lines, the problem goes away but then (of
    > course) I can undo the actions in "setUniqueProject" and that is
    > not good.
    >
    > Maybe there is an easier way to go about this entirely (I just want
    > to set the "Project" relationship of this object to the current
    > project when it is created ... can this really be that complicated?)
    >
    > Thanks!
    > Mike
  • On Sep 20, 2007, at 4:13 PM, Michael Burns wrote:

    > I'm having a strange problem while trying to set a value for an
    > NSManagedObject in its "awakeFromInsert" method.  The code is as
    > follows:
    >
    > - (void)awakeFromInsert
    > {
    > [super awakeFromInsert];
    >
    > NSManagedObjectContext* moc = [self managedObjectContext];
    >
    > // don't allow this new object to be undone
    > [moc processPendingChanges];
    >
    > [[moc undoManager] disableUndoRegistration];
    >
    > [self performSelector:@selector(setUniqueProject) withObject:nil
    > afterDelay:0];
    >
    > // turn the undo manager back on
    > [moc processPendingChanges];
    > [[moc undoManager] enableUndoRegistration];
    > }
    >
    > The object is inserted by pressing a button binded to an
    > NSArrayController who's set is binded to an NSTableView.  The
    > problem is that when I run this code as is, it creates two
    > identical objects in the table view.  If I comment out the two [moc
    > processPendingChanges] lines, the problem goes away but then (of
    > course) I can undo the actions in "setUniqueProject" and that is
    > not good.

    As Tony mentioned, processPendingChanges inside of -awake<xxx> is a
    bad idea.

    But it is unclear to me from your code snippet what you are trying to
    disable undo registration for in the first place?

    You've disabled undo registration, scheduled a perform delayed
    selector (in other words, do this later, not right now), then re-
    enabled undo registration. It isn't clear to me what you intended to
    do, but I think whatever it was, it isn't what your code is doing :-).

    Jim
  • The performSelector bit was part of what I thought might have been a
    solution to my problem.  Here's what I'm really trying to do:

    This is an NSPersistentDocument based application.  Every time a new
    document is created, an entity called a "Project" is created which
    stores all sorts of global data relevant to that document.  The
    awakeFromInsert from below is for another NSManagedObject
    ("WBObject") which is created through cocoa bindings as I explained.
    The "Project" entity has a to-many relationship called "objects" and
    the "WBObject" entity has a to-one relationship called "project".
    All I am trying to do is set up that relationship when a new
    "WBObject" object is created.

    Does that make sense?

    On Sep 20, 2007, at 4:59 PM, Jim Correia wrote:

    > On Sep 20, 2007, at 4:13 PM, Michael Burns wrote:
    >
    >> I'm having a strange problem while trying to set a value for an
    >> NSManagedObject in its "awakeFromInsert" method.  The code is as
    >> follows:
    >>
    >> - (void)awakeFromInsert
    >> {
    >> [super awakeFromInsert];
    >>
    >> NSManagedObjectContext* moc = [self managedObjectContext];
    >>
    >> // don't allow this new object to be undone
    >> [moc processPendingChanges];
    >>
    >> [[moc undoManager] disableUndoRegistration];
    >>
    >> [self performSelector:@selector(setUniqueProject) withObject:nil
    >> afterDelay:0];
    >>
    >> // turn the undo manager back on
    >> [moc processPendingChanges];
    >> [[moc undoManager] enableUndoRegistration];
    >> }
    >>
    >> The object is inserted by pressing a button binded to an
    >> NSArrayController who's set is binded to an NSTableView.  The
    >> problem is that when I run this code as is, it creates two
    >> identical objects in the table view.  If I comment out the two
    >> [moc processPendingChanges] lines, the problem goes away but then
    >> (of course) I can undo the actions in "setUniqueProject" and that
    >> is not good.
    >
    > As Tony mentioned, processPendingChanges inside of -awake<xxx> is a
    > bad idea.
    >
    > But it is unclear to me from your code snippet what you are trying
    > to disable undo registration for in the first place?
    >
    > You've disabled undo registration, scheduled a perform delayed
    > selector (in other words, do this later, not right now), then re-
    > enabled undo registration. It isn't clear to me what you intended
    > to do, but I think whatever it was, it isn't what your code is
    > doing :-).
    >
    > Jim

    Mike Burns
    Production Associate/Audio Specialist
    Modlin Center for the Arts
    University of Richmond

    Office - 804-287-6618
    Cell    - 804-370-4946
    Fax    - 804-287-6031
  • On Sep 20, 2007, at 5:07 PM, Michael Burns wrote:

    > The performSelector bit was part of what I thought might have been
    > a solution to my problem.  Here's what I'm really trying to do:
    >
    > This is an NSPersistentDocument based application.  Every time a
    > new document is created, an entity called a "Project" is created
    > which stores all sorts of global data relevant to that document.
    > The awakeFromInsert from below is for another NSManagedObject
    > ("WBObject") which is created through cocoa bindings as I
    > explained.  The "Project" entity has a to-many relationship called
    > "objects" and the "WBObject" entity has a to-one relationship
    > called "project".  All I am trying to do is set up that
    > relationship when a new "WBObject" object is created.

    Setting the initial values in -awakeFromInsert is a reasonable thing
    to do. Why are you trying to disable undo registration though?

    BTW, if there is really only ever one instance of Project in a
    document, it seems odd to model a relationship between every WBObject
    and the project; the modeled relationship doesn't appear to be
    providing any new or useful information to the object graph.

    Jim
  • On Sep 20, 2007, at 5:20 PM, Jim Correia wrote:

    >
    >
    > Setting the initial values in -awakeFromInsert is a reasonable
    > thing to do. Why are you trying to disable undo registration though?
    >
    > BTW, if there is really only ever one instance of Project in a
    > document, it seems odd to model a relationship between every
    > WBObject and the project; the modeled relationship doesn't appear
    > to be providing any new or useful information to the object graph.
    >
    > Jim
    >

    If I don't disable undo registration when the user attempts to undo
    after creating a new WBObject they undo the setting of the project
    relationship first.  (They have to undo twice to actually undo the
    creation of the WBObject itself).  I want the setting of the initial
    values to be grouped with the creation of the WBObject as far as the
    undo manager is concerned.

    As for the second part of your response, there are certain values
    that reside in the project object that need to be accessed by the
    WBObject's at some point.

    Mike
  • On Sep 20, 2007, at 6:09 PM, Mike Burns wrote:

    > If I don't disable undo registration when the user attempts to undo
    > after creating a new WBObject they undo the setting of the project
    > relationship first.  (They have to undo twice to actually undo the
    > creation of the WBObject itself).  I want the setting of the
    > initial values to be grouped with the creation of the WBObject as
    > far as the undo manager is concerned.

    Absent any intervention on your part (via processPendingChanges) all
    work which is done on one event loop cycle (in other words, as the
    result of the user clicking on the "New WBObject" button) will be
    grouped in on undo group.

    My suggestion is to roll back your code to do the simplest thing
    possible - just set the values in awakeFromInsert directly; no
    delayed performs, etc. Then re-evaluate where you are and what
    additional code is required.

    It sounds like you have a very simple problem being made complex by
    compounded hacks...

    > As for the second part of your response, there are certain values
    > that reside in the project object that need to be accessed by the
    > WBObject's at some point.

    It sounds like the project is a document singleton/global. That the
    objects need to access its properties still don't sound like a
    compelling or correct reason to model a relationship between WBObject
    and Project and pay the cost of maintaining those relationships and
    their inverses. If this is the case, you should just fetch the
    Project directly and use it when you need it. This has the side
    effect that it also eliminates the problem you are having about (but
    you should still understand why your code is wrong before you delete
    it.)

    Jim
  • On Sep 20, 2007, at 6:18 PM, Jim Correia wrote:
    >
    > It sounds like the project is a document singleton/global. That the
    > objects need to access its properties still don't sound like a
    > compelling or correct reason to model a relationship between
    > WBObject and Project and pay the cost of maintaining those
    > relationships and their inverses. If this is the case, you should
    > just fetch the Project directly and use it when you need it. This
    > has the side effect that it also eliminates the problem you are
    > having about (but you should still understand why your code is
    > wrong before you delete it.)
    >
    > Jim

    You make some good points there, and after reevaluating my code I can
    see how getting rid of that project relationship would make more
    sense for my purposes.  (I have other managed objects like the
    WBObject with that same relationship and the same goes for them.)
    But in the interest of figuring out what isn't working, I remember
    why I asked that question in the first place.  The following code
    creates the same problem that "processPendingChanges" seemed to cause
    in the first place (duplicating the newly created object):

    - (void)awakeFromInsert
    {
        [super awakeFromInsert];

    NSManagedObjectContext* moc = [self managedObjectContext];
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        NSError *fetchError = nil;
        NSArray *fetchResults;

        @try
    {
            NSEntityDescription *entity = [NSEntityDescription
    entityForName:@"Project" inManagedObjectContext:moc];
            [fetchRequest setEntity:entity];
      fetchResults = [moc executeFetchRequest:fetchRequest
    error:&fetchError];
        }
    @finally
    {
            [fetchRequest release];
        }

        if ((fetchResults != nil) && ([fetchResults count] == 1) &&
    (fetchError == nil))
    {
            [self setValue:[fetchResults objectAtIndex:0] forKey:
    @"project"];
        }

        if (fetchError != nil)
    {
            [self presentError:fetchError];
        }
        else
    {
            // should present custom error message...
        }
    }

    Does executing a fetch request cause that same problem?  And if so,
    what is the best work around?

    Thanks!

    Mike
  • On Sep 20, 2007, at 2:07 PM, Michael Burns wrote:

    > This is an NSPersistentDocument based application.  Every time a new
    > document is created, an entity called a "Project" is created which
    > stores all sorts of global data relevant to that document.  The
    > awakeFromInsert from below is for another NSManagedObject
    > ("WBObject") which is created through cocoa bindings as I explained.
    >
    First, it's not immediately clear why you can't use the pattern shown
    in the Persistent Document Tutorial -- unless the user has to perform
    the button click?  In which case, it seems it would be easier to
    manage control of creation of new objects from the controller (as
    ever, just because you're using bindings doesn't mean you don't have
    to write code and can't subclass... <http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/Cla
    sses/NSObjectController_Class/Reference/Reference.html#//apple_ref/occ/inst
    m/NSObjectController/newObject
    >).

    mmalc
  • Have the core data docs people though about a section specifically explaining
    what you should and shouldn't do when and where. Maybe in the context of the
    object and document life cycles. Including the gotchas like the side effects of
    undo redo etc... I really could have used some architecture guidance with my
    first CD projects.

    I am tempted to gather a list of such issues and post them somewhere as a "best
    practices" guide. However it would be better if someone more qualified would
    take on the job.

    Brian

    --- Tony Becker <aebecker...> wrote:

    > Until the awakeFromInsert is finished, the object is "incomplete"
    > Calling processPendingChanges flushes (effectively commits) this
    > incomplete object to the DB (thats a bad thing)
    > Then, after awakeFromInsert, the real object is inserted, which
    > appears as a dupe.
    >
    > You must not call processPendingChanges in awakeFromInsert or
    > awakeFromFetch.
    > For the same reasons, you can't call executeFecthRequest (which calls
    > processPendingChanges), in either of the same awake methods.
    >
    > Under various conditions, you'll get two objects - and have really
    > strange undo problems (even if you don't call the undo registration
    > code), which I suspect you're having.
    >
    > Call your undo registration code after the object is inserted into
    > the context.
    >
    >
  • On Sep 20, 2007, at 5:56 PM, Brian Williams wrote:

    > Have the core data docs people though about a section specifically
    > explaining
    > what you should and shouldn't do when and where. Maybe in the
    > context of the
    > object and document life cycles. Including the gotchas like the side
    > effects of
    > undo redo etc... I really could have used some architecture guidance
    > with my
    > first CD projects.
    >
    As ever, if there are areas which you think need additional
    explanation, please file a bug report.

    One of the problems, though, with specifying "what you should and
    shouldn't do when and where" is that it's at least sometimes context-
    dependent.  And people have a tendency not to consider the context --
    "Oh, the docs said that, so {I'll do, I won't do} it".  It's not
    possible to elaborate all the possible circumstances in which API
    might be used.  *In general*, it is only realistic to describe
    principles and guidance as to how they should be applied.  Moreover,
    particularly with Core Data and other high-level technologies, the
    documentation is unlikely to repeat information that is considered to
    be prerequisite, and it probably won't describe interactions between
    technologies except where there are significant differences in
    behaviour (e.g. the Core Data documentation won't in general describe
    interaction with key-value observing except for cases such as the fact
    that managed objects do not adopt automatic KVO)...

    mmalc
  • On Sep 20, 2007, at 6:32 PM, Mike Burns wrote:

    > You make some good points there, and after reevaluating my code I
    > can see how getting rid of that project relationship would make
    > more sense for my purposes.  (I have other managed objects like the
    > WBObject with that same relationship and the same goes for them.)
    > But in the interest of figuring out what isn't working, I remember
    > why I asked that question in the first place.  The following code
    > creates the same problem that "processPendingChanges" seemed to
    > cause in the first place (duplicating the newly created object):

    [...]

    > Does executing a fetch request cause that same problem?

    You previously discovered that -processPendingChanges inside of -
    awakeFromInsert was problematic. Turns out that fetching is
    problematic for the same reason; Core Data does equivalent work to -
    processPendingChanges right before doing a fetch.

    The documentation indirectly tells us that you shouldn't fetch from
    inside -awakeFromInsert. It says

    <http://developer.apple.com/documentation/Cocoa/Reference/
    CoreDataFramework/Classes/NSManagedObject_Class/Reference/
    Reference.html
    >

    Methods to Override Considerations

    The following methods are intended to be fine grained and not perform
    large scale operations. You must not fetch or save in these methods. In
    particular, they should not have side effects on the managed object
    context:

      • initWithEntity:insertIntoManagedObjectContext:
      • didTurnIntoFault
      • dealloc

    -awakeFromInsert is not in the list, but you can infer that it is
    disallowed since it happens from within one of the disallowed methods.

    > And if so, what is the best work around?

    Whichever one fits in best with your code, and doesn't require a
    fetch to be executed as part of object insertion.

    You can prefetch and store the Project when you setup your document,
    or fetch it on demand before inserting the object. Your custom
    subclass of NSManagedObject can have a convenience method

    + (id)insertNewWBObjectWithProject:(NSManagedObject *)project
    InManagedObjectContext:(NSManagedObjectContext *)context;

    To make this easier. Remember, just because there is an -insert:
    action method on the array controller doesn't mean you have to use
    it. Often it is appropriate to have the target of your button be your
    own action method where you write custom code to insert the object.

    Jim
  • On Sep 20, 2007, at 10:26 PM, mmalc crawford wrote:

    > On Sep 20, 2007, at 5:56 PM, Brian Williams wrote:
    >
    >> Have the core data docs people though about a section specifically
    >> explaining
    >> what you should and shouldn't do when and where. Maybe in the
    >> context of the
    >> object and document life cycles. Including the gotchas like the
    >> side effects of
    >> undo redo etc... I really could have used some architecture
    >> guidance with my
    >> first CD projects.
    >
    > As ever, if there are areas which you think need additional
    > explanation, please file a bug report.

    And so that people don't go away thinking that filing bugs against
    the documentation is a waste of effort, let me say that over the
    years I've had generally good success with filing bugs against the
    documentation and seeing it evolve in response to that feedback.

    To be fair to the docs, the documentation does contain advice about
    this issue, but it exists in the subclassing NSManagedObject notes.

    It says -initWithEntity:insertIntoManagedObjectContext: must not
    fetch or have side effects on the managed object context. -
    awakeFromInsert isn't mentioned, but since this happens as a side
    effect of insertion, it is not unreasonable to draw the conclusion
    yourself that you are subject to the same constraints.

    Jim
  • I filed a bug report requesting a best practices document, and
    included this issue.
    Another, similar issue is calling save: in observeChange, which calls
    processpendingChanges, which calls observeChange:, ...

    Basically, you need to be aware of what the methods do, to prevent
    recursion in the stack, which produces undesirable results.

    On Sep 20, 2007, at 10:26 PM, mmalc crawford wrote:

    >
    > On Sep 20, 2007, at 5:56 PM, Brian Williams wrote:
    >
    >> Have the core data docs people though about a section specifically
    >> explaining
    >> what you should and shouldn't do when and where. Maybe in the
    >> context of the
    >> object and document life cycles. Including the gotchas like the
    >> side effects of
    >> undo redo etc... I really could have used some architecture
    >> guidance with my
    >> first CD projects.
    >>
    > As ever, if there are areas which you think need additional
    > explanation, please file a bug report.
    >
    > One of the problems, though, with specifying "what you should and
    > shouldn't do when and where" is that it's at least sometimes
    > context-dependent.  And people have a tendency not to consider the
    > context -- "Oh, the docs said that, so {I'll do, I won't do} it".
    > It's not possible to elaborate all the possible circumstances in
    > which API might be used.  *In general*, it is only realistic to
    > describe principles and guidance as to how they should be applied.
    > Moreover, particularly with Core Data and other high-level
    > technologies, the documentation is unlikely to repeat information
    > that is considered to be prerequisite, and it probably won't
    > describe interactions between technologies except where there are
    > significant differences in behaviour (e.g. the Core Data
    > documentation won't in general describe interaction with key-value
    > observing except for cases such as the fact that managed objects do
    > not adopt automatic KVO)...
    >
    > mmalc
previous month september 2007 next month
MTWTFSS
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Go to today