Key Value Observing?

  • Hello,

    My Core Data (not document-based) Point-of-Sale App has Sales which have
    LineItems which have price, quantity and a calculated total.  The
    LineItems are bound to an NSArrayController which is displayed in an
    NSTableView. Sale calculates a total of its LineItems totals. The Sales
    total is displayed in an NSTextView on the Sale window.

    Currently, when a user changes a LineItem's price or quantity the
    displayed Sales total is not automatically updated.  However, when the
    user changes the price or quantity and then clicks on another LineItem
    in the LineItem NSTableView, the Sales total *is* updated.

    After trying and failing to make the update automatic I added a
    'Recalculate' button to my Sale Window which calls arrangeObjects
    on the LineItem array controller.  Not nice.

    What is the best way of making the Sales Total update automatically
    whenever the LineItem salePrice or quantity change?

    Having studied Apple's KVO docs, Googled KVO and examined mmalc's
    instructive GraphicBindings application, I still can't grasp how to
    automatically update the Sale total when a LineItem attribute changes.
    For example, I've added the following to LineItem:

    + (void)
    initialize
    {
        [self setKeys: [NSArray arrayWithObjects: @"quantity",
                                                  @"salePrice",
                                                  nil]
            triggerChangeNotificationsForDependentKey: @"total"];
    }

    but couldn't figure out a way to add something similar to Sale, like:

    + (void)
    initialize
    {
        [self setKeys: [NSArray arrayWithObjects: @"lineItems",
                                                  nil]
            triggerChangeNotificationsForDependentKey: @"total"]; // sale total
    }

    which might not even be valid and even if it were, it doesn't look
    deeper into the lineitem attributes: quantity and salePrice.

    Of course, my application is actually more complicated than I've
    indicated and although the default KVO works amazingly well, I have
    similar update issues elsewhere.

    Looking at mmalc's GraphicBindings app, I get a sense that I could solve
    the problem if I were willing to subclass NSArrayController for each
    entity that has to-many relationships.  But, I already have a dozen
    NSArrayController subclasses (to provide custom filtering). Subclassing
    them further would seem excessive.

    I'm also hesitant to subclass NSTextView for this purpose -- it doesn't
    seem that it should be necessary.

    I've found posts similar to this one in the archives of this list and
    others but have not found answers.  Any guidance would be appreciated.

    Cheers,

    Steve
  • On Dec 11, 2007, at 4:32 AM, Steve Steinitz wrote:

    [Summary: I have a key whose value is dependent on values of
    attributes in a related entity—how do I ensure it is kept up to date
    as the attribute values are changes and as the relationship is
    manipulated?]

    See <http://developer.apple.com/documentation/Cocoa/Conceptual/CoreData/Articles
    /cdFAQ.html#//apple_ref/doc/uid/TP40001802-DontLinkElementID_24
    >.

    mmalc
  • Le 11 déc. 07 à 17:53, <cocoa-dev-request...> a écrit :

    > [Summary: I have a key whose value is dependent on values of
    > attributes in a related entity—how do I ensure it is kept up to date
    > as the attribute values are changes and as the relationship is
    > manipulated?]
    >
    > See <http://developer.apple.com/documentation/Cocoa/Conceptual/CoreData/Articles
    /cdFAQ.html#/
    > /apple_ref/doc/uid/TP40001802-DontLinkElementID_24
    >> .
    >
    > mmalc

    This FAQ seems out of date (oct. 2007): the cocoa doc states that
    setKeys:triggerChangeNotificationsForDependentKey: is a "now-
    deprecated" method and, in fact, despite the ideas in the faq, I was
    never able to make it work for inter-entity dependances.

    On the other hand, the new keyPathsForValuesAffectingValueForKey:
    method lacks good and complete code examples. I just sent a
    documentation improvement request on the faq’s page.

    Flofl.
  • --- Wdyp <wdyp...> wrote:

    >
    > Le 11 déc. 07 à 17:53,
    > <cocoa-dev-request...> a écrit :
    >
    >> [Summary: I have a key whose value is dependent on
    > values of
    >> attributes in a related entity—how do I ensure it
    > is kept up to date
    >> as the attribute values are changes and as the
    > relationship is
    >> manipulated?]
    >>
    >> See
    >
    <http://developer.apple.com/documentation/Cocoa/Conceptual/CoreData/Articles
    /cdFAQ.html#/
    >
    >> /apple_ref/doc/uid/TP40001802-DontLinkElementID_24
    >>> .
    >>
    >> mmalc
    >
    >
    > This FAQ seems out of date (oct. 2007): the cocoa
    > doc states that
    > setKeys:triggerChangeNotificationsForDependentKey:
    > is a "now-
    > deprecated" method and, in fact, despite the ideas
    > in the faq, I was
    > never able to make it work for inter-entity
    > dependances.

    I think you need to reread that. The FAQ says you
    *can't* use
    setKeys:triggerChangeNotificationsForDependentKey:. It
    then goes on to explain how you can do this with
    notifications.

    Cheers,
    Chuck

          ____________________________________________________________________________________
    Be a better friend, newshound, and
    know-it-all with Yahoo! Mobile.  Try it now.  http://mobile.yahoo.com/;_ylt=Ahu06i62sR8HDtDypao8Wcj9tAcJ
  • On Dec 11, 2007, at 11:00 AM, Wdyp wrote:

    > This FAQ seems out of date (oct. 2007): the cocoa doc states that
    > setKeys:triggerChangeNotificationsForDependentKey: is a "now-
    > deprecated" method and, in fact, despite the ideas in the faq, I was
    > never able to make it work for inter-entity dependances.
    >
    The FAQ states that setKeys:triggerChangeNotificationsForDependentKey:
    will *not* work, so that the API is deprecated isn't really relevant.
    The same issue applies to keyPathsForValuesAffectingValueForKey:,
    although the documentation should make that clear too.

    The principles behind the solution described are fairly
    straightforward...

    mmalc
  • You might look at this:  http://objectiveclips.com

    There is a sample that does just this.  Disclaimer, I wrote it.
    -Todd Blanchard

    On Dec 11, 2007, at 4:32 AM, Steve Steinitz wrote:

    > Hello,
    >
    > My Core Data (not document-based) Point-of-Sale App has Sales which
    > have
    > LineItems which have price, quantity and a calculated total.  The
    > LineItems are bound to an NSArrayController which is displayed in an
    > NSTableView. Sale calculates a total of its LineItems totals. The
    > Sales
    > total is displayed in an NSTextView on the Sale window.
    >
    > Currently, when a user changes a LineItem's price or quantity the
    > displayed Sales total is not automatically updated.  However, when the
    > user changes the price or quantity and then clicks on another LineItem
    > in the LineItem NSTableView, the Sales total *is* updated.
    >
    > After trying and failing to make the update automatic I added a
    > 'Recalculate' button to my Sale Window which calls arrangeObjects
    > on the LineItem array controller.  Not nice.
    >
    > What is the best way of making the Sales Total update automatically
    > whenever the LineItem salePrice or quantity change?
    >
    > Having studied Apple's KVO docs, Googled KVO and examined mmalc's
    > instructive GraphicBindings application, I still can't grasp how to
    > automatically update the Sale total when a LineItem attribute changes.
    > For example, I've added the following to LineItem:
    >
    > + (void)
    > initialize
    > {
    > [self setKeys: [NSArray arrayWithObjects: @"quantity",
    > @"salePrice",
    > nil]
    > triggerChangeNotificationsForDependentKey: @"total"];
    > }
    >
    > but couldn't figure out a way to add something similar to Sale, like:
    >
    > + (void)
    > initialize
    > {
    > [self setKeys: [NSArray arrayWithObjects: @"lineItems",
    > nil]
    > triggerChangeNotificationsForDependentKey: @"total"]; // sale
    > total
    > }
    >
    > which might not even be valid and even if it were, it doesn't look
    > deeper into the lineitem attributes: quantity and salePrice.
    >
    > Of course, my application is actually more complicated than I've
    > indicated and although the default KVO works amazingly well, I have
    > similar update issues elsewhere.
    >
    > Looking at mmalc's GraphicBindings app, I get a sense that I could
    > solve
    > the problem if I were willing to subclass NSArrayController for each
    > entity that has to-many relationships.  But, I already have a dozen
    > NSArrayController subclasses (to provide custom filtering).
    > Subclassing
    > them further would seem excessive.
    >
    > I'm also hesitant to subclass NSTextView for this purpose -- it
    > doesn't
    > seem that it should be necessary.
    >
    > I've found posts similar to this one in the archives of this list and
    > others but have not found answers.  Any guidance would be appreciated.
    >
    > Cheers,
    >
    > Steve
  • Hi mmalc

    Thanks for your reply.  I have a question, a confession and a comment.

    On 11/12/07, mmalc crawford wrote:

    > [Summary: I have a key whose value is dependent on values of attributes in a related entity — how do I ensure it is kept up to date >  as the attribute values are changes and as the relationship is >  manipulated?]

    > See ...CoreData/Articles/cdFAQ.html#//apple_ref/doc/uid/TP40001802-DontLinkElementID_24

    The description is a bit sparse but one can read between the
    lines. Its not the most straightforward thing to set up but it
    does seem to work.

    One thing i'm not clear on:

    The article says 'You must add and remove the parent as an
    observer as child objects are added to and removed from the
    relationship'.  Inconvenient but fair enough.  However, that
    implies that when a parent is instantiated it needs to loop
    through all its existing children and register to observe their
    innards.  If that is the case, wouldn't it give a performance
    hit for any application, like mine, that load tens of thousands
    of parent objects at startup.

    Would that be correct?

    Let me share an evil hack I've done while I await the answer to
    those questions.  I didn't set Sale up as an observer for the
    LineItems at all -- reluctant to code and debug it and debug the
    registering / unregistering in the absence of answers.  Instead,
    I just overrode

        LineItem setQuantity and
        LineItem setSalePrice

    to call my brand new dummy

        Sale setTotal

    method which does nothing more than call didChange (Sale has no
    total ivar/attribute and the calculation is done in Sale
    total).  Bingo.  Maintenance and encapsulation aside, I'm out of
    hot water with my client.

    I'd like to mention that the article you (mmalc) referenced has
    made me realize the performance improvements available by
    calculating the Sale total, for example, only when something
    causes it to change.  I'm currently calculating it everytime
    anyone asks for it.  Time for some re-engineering and custom KVO?

    Thanks again,

    Steve
  • On Dec 11, 2007, at 10:31 PM, Steve Steinitz wrote:

    > The article says 'You must add and remove the parent as an observer
    > as child objects are added to and removed from the relationship'.
    > Inconvenient but fair enough.  However, that implies that when a
    > parent is instantiated it needs to loop through all its existing
    > children and register to observe their innards.  If that is the
    > case, wouldn't it give a performance hit for any application, like
    > mine, that load tens of thousands of parent objects at startup.
    > Would that be correct?
    >
    For the first implementation, it may be... If measurement determines
    that this is a significant overhead, then it may be more appropriate
    to adopt the second approach:

    "You can register the parent with the application's notification
    center as an observer of its managed object context. The parent should
    respond to relevant change notifications posted by the children in a
    manner similar to that for key-value observing."

    > Let me share an evil hack I've done while I await the answer to
    > those questions.  I didn't set Sale up as an observer for the
    > LineItems at all -- reluctant to code and debug it and debug the
    > registering / unregistering in the absence of answers.  Instead, I
    > just overrode
    > LineItem setQuantity and
    > LineItem setSalePrice
    > to call my brand new dummy
    > Sale setTotal
    > method which does nothing more than call didChange (Sale has no
    > total ivar/attribute and the calculation is done in Sale total).
    > Bingo.  Maintenance and encapsulation aside, I'm out of hot water
    > with my client.
    >
    I wouldn't recommend doing that...
    At the very least you should always invoke the willChange method
    first.  Second, you shouldn't invoke the change notification methods
    after the change has already been made just as a way of "tickling" an
    update.

    You *might* get away with:

    - (void)setSalePrice:(NSNumber *)newPrice
    {
        if (![[self salePrice] isEqualToNumber:newPrice])
        {
            [[self sale] willChangeValueForKey:@"total"];
            [self willChangeValueForKey:@"salePrice"];
            [self setPrimitiveValue:newPrice forKey:@"salePrice"];
            [self didChangeValueForKey:@"salePrice"];
            [[self sale] didChangeValueForKey:@"total"];
        }
    }

    (typed in Mail).

    I need to check, though...

    mmalc
previous month december 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
31            
Go to today