Forcing Core Data to save attribute changed behind its back?

  • Hi all,

    I have a managed object where one of the attributes is quite large and so when I change it, instead of the usual setAttribute:newValue I mutate the object directly.  Of course, Core Data does not know that I've done this.  I'm looking for a way to tell it.  (Otherwise, if the thing is mutated after the document is first saved, Core Data cleverly skips updating this attribute.)

    I've tried:
    - setAttribute:sameValue
    - will/didChangeValueForKeyPath: but that's not a sufficient 'kick'

    Any other suggestions?

    Thanks,

    --
    ____________________________________________________________
    Sean McBride, B. Eng                <sean...>
    Rogue Research                        www.rogue-research.com
    Mac Software Developer              Montréal, Québec, Canada
  • On Mon, Jul 23, 2012 at 3:03 PM, Sean McBride <sean...> wrote:
    > when I change it, instead of the usual setAttribute:newValue I mutate the object directly.
    > - will/didChangeValueForKeyPath: but that's not a sufficient 'kick'

    Out of random curiousity, does this big mutable object you're updating
    change in a way Core Data would see? Does the object's "before" state
    -isEqual: its "after" state?
  • What do you try to win by changing the attribute bind CD's back?

    If you want to take it out of undo you might switch it off temporarily (e.g. [undoManager disableUndoRegistration], or store the undo manager in a local, set the context's undo to nil, and restore it - which may or may not cause trouble depending on whats happening in your object graph. I use GCUndoManager to stay away from such trouble.)

    Apart from that there is

    - (void)refreshObject:(NSManagedObject *)object mergeChanges:(BOOL)flag

    as well as the

    - (void)setPrimitiveValue:(id)value forKey:(NSString *)key

    methods, depending on what you intend to achieve.

    Dunno if these might help.

    Am 24.07.2012 um 00:03 schrieb Sean McBride:

    > Hi all,
    >
    > I have a managed object where one of the attributes is quite large and so when I change it, instead of the usual setAttribute:newValue I mutate the object directly.  Of course, Core Data does not know that I've done this.  I'm looking for a way to tell it.  (Otherwise, if the thing is mutated after the document is first saved, Core Data cleverly skips updating this attribute.)
    >
    > I've tried:
    > - setAttribute:sameValue
    > - will/didChangeValueForKeyPath: but that's not a sufficient 'kick'
    >
    > Any other suggestions?
    >
    > Thanks,
    >
    > --

    ___ Peter Hartmann ________

    mailto:<hphartmann...>
  • On Tue, 24 Jul 2012 15:15:16 +0200, Peter Hartmann said:

    > What do you try to win by changing the attribute bind CD's back?

    Run time performance.  The data is large and changes frequently.

    > If you want to take it out of undo you might switch it off temporarily
    > (e.g. [undoManager disableUndoRegistration], or store the undo manager
    > in a local, set the context's undo to nil, and restore it - which may or
    > may not cause trouble depending on whats happening in your object graph.
    > I use GCUndoManager to stay away from such trouble.)

    In fact, I want undo, and to support it, I had to do extra work.  (I use GCUndoManager too.)  That's working fine.  The problem is that saving doesn't save.

    > Apart from that there is
    >
    > - (void)refreshObject:(NSManagedObject *)object mergeChanges:(BOOL)flag

    From what I can tell from the docs, this is more to update the object *from* the store.  I need the opposite: to convince Core Data to write my object's attributes to the store (when I invoke 'save').

    > as well as the
    >
    > - (void)setPrimitiveValue:(id)value forKey:(NSString *)key

    This doesn't trigger Core Data to think there is a change, and neither does regular accessors.

    Thanks,

    --
    ____________________________________________________________
    Sean McBride, B. Eng                <sean...>
    Rogue Research                        www.rogue-research.com
    Mac Software Developer              Montréal, Québec, Canada
  • On Tue, 24 Jul 2012 05:31:22 -0700, Sixten Otto said:

    >> when I change it, instead of the usual setAttribute:newValue I mutate
    > the object directly.
    >> - will/didChangeValueForKeyPath: but that's not a sufficient 'kick'
    >
    > Out of random curiousity, does this big mutable object you're updating
    > change in a way Core Data would see?

    Apparently not, that's the problem. :)

    > Does the object's "before" state
    > -isEqual: its "after" state?

    Yes.  My object is a subclass of NSObject and I don't override isEqual:.  As I test, I overrode it and always return NO.  At first, I thought this did the trick, since Core Data passed through this and saved properly; but alas, it only seems to go through the path once after the document is opened, not every time I ask it to save.

    Thanks,

    --
    ____________________________________________________________
    Sean McBride, B. Eng                <sean...>
    Rogue Research                        www.rogue-research.com
    Mac Software Developer              Montréal, Québec, Canada
  • On Tue, Jul 24, 2012 at 11:27 AM, Sean McBride <sean...> wrote:
    > Yes.  My object is a subclass of NSObject and I don't override isEqual:.  As I test, I overrode it and always return NO.  At first, I thought this did the trick, since Core Data passed through this and saved properly; but alas, it only seems to go through the path once after the document is opened, not every time I ask it to save.

    Are you still also triggering the KVO notices and/or using the setter?

    My thinking was that you probably need both things: first, to take an
    action that causes Core Data to notice you're changing the value (like
    calling -will/didChangeValueForKeyPath:), and second, that when Core
    Data compares the before and after values, that they are not -isEqual:
    to one another.

    (But, to be clear, this is untested supposition on my part.)
  • On Jul 23, 2012, at 15:03 , Sean McBride wrote:

    > I have a managed object where one of the attributes is quite large and so when I change it, instead of the usual setAttribute:newValue I mutate the object directly.  Of course, Core Data does not know that I've done this.  I'm looking for a way to tell it.  (Otherwise, if the thing is mutated after the document is first saved, Core Data cleverly skips updating this attribute.)
    >
    > I've tried:
    > - setAttribute:sameValue
    > - will/didChangeValueForKeyPath: but that's not a sufficient 'kick'

    I believe the answer is that you're Doing It Wrong™. :)

    Core Data attributes are based on value-objects -- that is, Core Data by design assumes that attribute values are immutable (at least as far as what they represent, even if not literally immutable value objects)**. I vaguely remember this coming up on this list a couple of years ago. The only documentation reference I can find now is this:

    https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Cor
    eData/Articles/cdUsingMOs.html#//apple_ref/doc/uid/TP40001803-212651


    which says:

    > You must, however, change attribute values in a KVC-compliant fashion. For example, the following typically represents a programming error:
    >
    > NSMutableString *mutableString = [NSMutableString stringWithString:@"Stig"];
    > [newEmployee setFirstName:mutableString];
    > [mutableString setString:@"Laura"];
    >
    > For mutable values, you should either transfer ownership of the value to Core Data, or implement custom accessor methods to always perform a copy. The previous example may not represent an error if the class representing the Employee entity declared the firstName property (copy) (or implemented a custom setFirstName: method that copied the new value). In this case, after the invocation of setString: (in the third code line) the value of firstName would then still be “Stig” and not “Laura”.

    By "transfer ownership", this seems to mean "don't keep a reference to it, so that you don't change it, so that it's effectively immutable", which is my justification for the explanation I first gave.

    Another way of saying all this is that it may not be possible to (reliably) inform Core Data that an attribute has changed without changing the identity of the object that represents the value.

    ** I think the same is intended to be true of @properties that represent attributes generally, not just with Core Data. An attribute value is paradigmatically immutable, whereas a mutably-valued @property is really a to-one relationship, not an attribute. Core Data is just a stern follower of this principle.
  • On Tue, 24 Jul 2012 14:04:12 -0700, Quincey Morris said:

    > I believe the answer is that you're Doing It Wrong™. :)

    I know. :)  I only do it in one place, and only because the attribute is a few hundred MB, which I don't want to be copying all the time.

    (The official Core Data recommendations for dealing with large BLOBs have always been very hand-wavy, with little-to-no framework support for their recommendations.  In 10.7 we now at least have the 'store in external record' option, but still no support for packages in NSPersistentDocument.  Despite me storing this large BLOB directly, performance is reasonable enough.  Besides, the app has shipped, and, barring migration, I can't change my model anyway.)

    > For mutable values, you should either transfer ownership of the value
    > to Core Data, or implement custom accessor methods to always perform a
    > copy.

    Which I always do for things like strings of few hundred bytes...

    > Another way of saying all this is that it may not be possible to
    > (reliably) inform Core Data that an attribute has changed without
    > changing the identity of the object that represents the value.

    :(  And changing the identity means using a different object... hmmmm... I guess since my object is basically a fancy wrapper of NSMutableData, I could actually copy my object but not copy the composed NSData too...

    Cheers,

    --
    ____________________________________________________________
    Sean McBride, B. Eng                <sean...>
    Rogue Research                        www.rogue-research.com
    Mac Software Developer              Montréal, Québec, Canada
  • On Wed, Jul 25, 2012, at 03:54 PM, Sean McBride wrote:
    > On Tue, 24 Jul 2012 14:04:12 -0700, Quincey Morris said:
    >> Another way of saying all this is that it may not be possible to
    >> (reliably) inform Core Data that an attribute has changed without
    >> changing the identity of the object that represents the value.
    >
    > :(  And changing the identity means using a different object... hmmmm...
    > I guess since my object is basically a fancy wrapper of NSMutableData, I
    > could actually copy my object but not copy the composed NSData too...

    I was going to recommend something similar.

    The inability to express mutation of a heavyweight model object is a
    serious shortcoming in KVC/KVO. Most of the time it works; objects
    generate the right change notifications and observers pick them up. But
    then some intermediate observer gets smart and says "aha, the old and
    new values are pointer-equal! I don't need to forward this change
    notification for my derived keys!" and the entire thing breaks down.

    I've filed bugs asking for richer self-description of key types
    (basically -isOrderedToManyRelationshipKey: and the like). Perhaps we
    should also have -isMutableObjectKey:. Either that or the framework
    should stop eliding forwarded change notifications just because the
    values don't compare pointer-equal.

    --Kyle Sluder
  • On Tue, 24 Jul 2012 13:32:21 -0700, Sixten Otto said:

    > On Tue, Jul 24, 2012 at 11:27 AM, Sean McBride <sean...>
    > wrote:
    >> Yes.  My object is a subclass of NSObject and I don't override
    > isEqual:.  As I test, I overrode it and always return NO.  At first, I
    > thought this did the trick, since Core Data passed through this and
    > saved properly; but alas, it only seems to go through the path once
    > after the document is opened, not every time I ask it to save.
    >
    > Are you still also triggering the KVO notices and/or using the setter?

    Yes.

    > My thinking was that you probably need both things: first, to take an
    > action that causes Core Data to notice you're changing the value (like
    > calling -will/didChangeValueForKeyPath:), and second, that when Core
    > Data compares the before and after values, that they are not -isEqual:
    > to one another.
    >
    > (But, to be clear, this is untested supposition on my part.)

    I thought it was a very clever idea, alas it only half worked.  Like Kyle says, somewhere someone is doing pointer-equal checks.

    Thanks!

    --
    ____________________________________________________________
    Sean McBride, B. Eng                <sean...>
    Rogue Research                        www.rogue-research.com
    Mac Software Developer              Montréal, Québec, Canada
previous month july 2012 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 31          
Go to today