KVC Problems with Indexed Accessor for To-Many Properties

  • I'm having problems implementing KVC with indexed
    accessor for a To-Many property.  Here are some
    details...

    (1) I have a core data model

    (2) I have an entity called DataSet, which contains a
    binary attribute called data.  I want to support Key
    Value coding for a "fake array"
    in DataSet called dataPoints, which are
    NSMutableDictionary objects that will be extracted out
    of the data (NSData) according to the index.

    (3) Inside DataSet I have implemented...
    (a) - (unsigned int) countOfDataPoints
    (b) - (NSMutableDictionary *)
    objectInDataPointsAtIndex:(unsigned int)index
    (c) - (void) insertObject:(NSMutableDictionary *)
    dataPoint inDataPointsAtIndex:(unsigned int)index
    (d) - (void) removeObjectFromDataPointsAtIndex:
    (unsigned int)index

    (4) Inside MyDocument.nib I have an NSArrayController
    (a) set to Class Mode with Object Class Name of
    NSMutableDictionary
    (b) managedObjectContext bound to the File's Owner
    (c) contentArray bound to the selection.dataPoints of
    another NSArrayController whose content is DataSet
    objects.
    (d) and an NSTableView with columns bound to the
    NSArrayController with a keypaths of
    arrangedObjects.key1, arrangedObjects.key2, ... for
    each key inside the NSMutableDictionary.

    (5) I have Insert and Remove buttons connected to
    -insertDatum and -removeDatum methods in my code,
    which call ...
    - (void) insertObject:(NSMutableDictionary *)
    dataPoint inDataPointsAtIndex:(unsigned int)index
    - (void) removeObjectFromDataPointsAtIndex: (unsigned
    int) index
    and this works fine:  I can populate and remove rows
    from the tableview.

    My problem is:  When I edit a value (cell) in the
    table, once I hit return the cell goes back to it's
    original value.  I thought that my arrayController
    would call removeObjectFromDataPointsAtIndex followed
    by insertObject:inDataPointsAtIndex in this case, but
    they never get called by the array controller.  What
    am I missing here?  I can see that countOfDataPoints
    and objectInDataPointsAtIndex are being called by the
    arrayController, but not the others.

    Since dataPoints is a "fake" array, I can't put a -
    (NSArray *) dataPoints and - (void) setDataPoints
    method in my DataSet class.

    Thanks for advance, if you can give any advice.

    Philip

    __________________________________________________
    Do You Yahoo!?
    Tired of spam?  Yahoo! Mail has the best spam protection around
    http://mail.yahoo.com
  • On Oct 13, 2006, at 8:10 AM, joe OneNinetyTwo wrote:

    > My problem is:  When I edit a value (cell) in the
    > table, once I hit return the cell goes back to it's
    > original value.  I thought that my arrayController
    > would call removeObjectFromDataPointsAtIndex followed
    > by insertObject:inDataPointsAtIndex in this case, but
    > they never get called by the array controller.

    Editing a value in a table cell generally changes a property value of
    something *in* the array, not the array itself.

    If I have a table of "Person" objects and I enter a value for "First
    Name" in one of the rows, there's no need for the object to be
    removed and re-inserted in the array. It just updates the string
    value of firstName on that one object.

        - Scott
  • Thanks Scott!  Now it makes sense.

    When I edit a table cell, the NSArrayController simply
    updates the corresponding value inside the
    NSMutableDictionary, and then sends a
    objectInDataPointsAtIndex message, which was
    overwriting my edit with an NSMutableDictionary object
    created with the "faked" array.

    I was thinking the NSArrayController would use
    removeObjectFromDataPointsAtIndex followed by
    insertObject:inDataPointsAtIndex when a table cell was
    edited.

    I don't really see how I can do what I want using an
    NSArrayController.  It looks like I'll have to
    implement a data source for the NSTableView.

    Philip

    --- Scott Stevenson <scott...> wrote:

    >
    > On Oct 13, 2006, at 8:10 AM, joe OneNinetyTwo wrote:
    >
    >> My problem is:  When I edit a value (cell) in the
    >> table, once I hit return the cell goes back to
    > it's
    >> original value.  I thought that my arrayController
    >> would call removeObjectFromDataPointsAtIndex
    > followed
    >> by insertObject:inDataPointsAtIndex in this case,
    > but
    >> they never get called by the array controller.
    >
    > Editing a value in a table cell generally changes a
    > property value of
    > something *in* the array, not the array itself.
    >
    > If I have a table of "Person" objects and I enter a
    > value for "First
    > Name" in one of the rows, there's no need for the
    > object to be
    > removed and re-inserted in the array. It just
    > updates the string
    > value of firstName on that one object.
    >
    > - Scott
    > _______________________________________________
    > Do not post admin requests to the list. They will be
    > ignored.
    > Cocoa-dev mailing list
    > (<Cocoa-dev...>)
    > Help/Unsubscribe/Update your Subscription:
    >
    http://lists.apple.com/mailman/options/cocoa-dev/<joe192...>
    >
    > This email sent to <joe192...>
    >

    __________________________________________________
    Do You Yahoo!?
    Tired of spam?  Yahoo! Mail has the best spam protection around
    http://mail.yahoo.com
  • On Oct 13, 2006, at 6:28 PM, joe OneNinetyTwo wrote:

    > I don't really see how I can do what I want using an
    > NSArrayController.  It looks like I'll have to
    > implement a data source for the NSTableView.

    I found that usually when people say that they're wrong.  :)

    It might be easier overall to just create a simple class which has a
    dictionary inside. Bindings is a lot more valuable than it looks on
    the surface. Don't give it up easily.

        - Scott
  • Scott,

    I agree that bindings would be better.

    However, I need to avoid an array of objects, because
    my array of data points will likely contain millions
    of points.  That's why I am packing them into an
    NSData object, and thought I could write indexed
    accessor methods to use with an NSArrayController.
    If the table was read-only I would have been fine.

    Not sure what you mean by creating a simple class with
    a dictionary inside.  Why would that give me more
    flexibility over an NSMutableDictionary?  Seems like
    I would still need the arrayController to notifiy me
    with a modified object (table row), so I could update
    my NSData object correspondingly.  - Philip

    --- Scott Stevenson <scott...> wrote:
    >
    > On Oct 13, 2006, at 6:28 PM, joe OneNinetyTwo wrote:
    >
    >> I don't really see how I can do what I want using
    > an
    >> NSArrayController.  It looks like I'll have to
    >> implement a data source for the NSTableView.
    >
    > I found that usually when people say that they're
    > wrong.  :)
    >
    > It might be easier overall to just create a simple
    > class which has a
    > dictionary inside. Bindings is a lot more valuable
    > than it looks on
    > the surface. Don't give it up easily.
    >
    > - Scott

    __________________________________________________
    Do You Yahoo!?
    Tired of spam?  Yahoo! Mail has the best spam protection around
    http://mail.yahoo.com
  • On Oct 13, 2006, at 7:13 PM, joe OneNinetyTwo wrote:

    > Why would that give me more
    > flexibility over an NSMutableDictionary?  Seems like
    > I would still need the arrayController to notifiy me
    > with a modified object (table row), so I could update
    > my NSData object correspondingly.  - Philip

    The custom class can do whatever notifications are necessary when the
    propery is modified. That's how Core Data works, in fact.

        - Scott
  • Ok, that makes sense.  However, I tried for a few
    hours, and I still don't see a approach for
    implementing this idea.    Let's say I create a Datum
    object for my array controller, and this Datum (core
    data) object contains a NSMutableDictionary.
    Whenever the array controller needs to update a row in
    the table, it sends the message

    objectInDataPointsAtIndex:

    I've tried two different ways to write
    objectInDataPointsAtIndex: using Datum...

    (1) In this version of the method I extract the values
    out of my NSData object using the index, and then
    place them in the
    dictionary of a freshly created datum object, and
    return that object to the array Controller.  Problem
    with this approach
    is I end up generating thousands of Datum objects in
    my managedObject Space, with one created every time
    objectInDataPointsAtIndex is called.  I autorelease
    them right after creation, but they don't seem to
    disappear when
    the array controller is done with them.  Even if I
    could get rid of the unwanted datum objects, I think
    this approach still needs
    a datum object for every dataPoint, and if so, then it
    sort of defeats the purpose of using the indexed
    accessors for a "faked"
    array.  So, I tried the next approach...

    (2)  In this version of the method I extract the
    values out of my NSData object using the index, and
    then place them in the
    dictionary of a single global datum object, and return
    that object to the array Controller.  This approach
    resulted in an
    infinite loop (here's a taste below - read bottom up):

    #73210    0x000f1f04 in -[DataSet
    objectInDataPointsAtIndex:] at DataSet.m:97
    #73211    0x92a13760 in -[NSKeyValueArray objectAtIndex:]
    #73212    0x9395a850 in -[NSTableBinder
    _visibleRowIndexesForObject:]
    #73213    0x9395a1d4 in -[NSTableBinder
    observeValueForKeyPath:ofObject:change:context:]
    #73214    0x9296facc in
    -[NSObject(NSKeyValueObserverNotification)
    didChangeValueForKey:]
    #73215    0x907d0cc4 in CFDictionarySetValue
    #73216    0x929424a4 in -[NSCFDictionary
    setObject:forKey:]
    #73217    0x929ea6f4 in -[NSObject(NSKeyValueCoding)
    setValue:forKeyPath:]
    #73218    0x000f1f04 in -[DataSet
    objectInDataPointsAtIndex:] at DataSet.m:97
    #73219    0x92a13760 in -[NSKeyValueArray objectAtIndex:]
    #73220    0x9395a850 in -[NSTableBinder
    _visibleRowIndexesForObject:]
    #73221    0x9395a1d4 in -[NSTableBinder
    observeValueForKeyPath:ofObject:change:context:]
    #73222    0x9296facc in
    -[NSObject(NSKeyValueObserverNotification)
    didChangeValueForKey:]
    #73223    0x907d0cc4 in CFDictionarySetValue
    #73224    0x929424a4 in -[NSCFDictionary
    setObject:forKey:]
    #73225    0x929ea6f4 in -[NSObject(NSKeyValueCoding)
    setValue:forKeyPath:]
    #73226    0x000f1f04 in -[DataSet
    objectInDataPointsAtIndex:] at DataSet.m:97

    The array controller calls objectInDataPointsAtIndex
    which updates Datum's dictionary with values from
    NSData,
    and once the dictionary is updated, the array
    controller learns the datum has been updated, so it
    calls
    objectInDataPointsAtIndex again, ...

    I would love to stick with bindings, but it's still
    not obvious how I can do it with a mutable "faked"
    array and KVC indexed accessors.  - Philip

    --- Scott Stevenson wrote:

    > The custom class can do whatever notifications are
    > necessary when the
    > propery is modified. That's how Core Data works, in
    > fact.
    >
    > - Scott

    __________________________________________________
    Do You Yahoo!?
    Tired of spam?  Yahoo! Mail has the best spam protection around
    http://mail.yahoo.com
  • On Oct 14, 2006, at 10:48 AM, joe OneNinetyTwo wrote:

    > I would love to stick with bindings, but it's still
    > not obvious how I can do it with a mutable "faked"
    > array and KVC indexed accessors.  - Philip

    So you're technically doing what I suggested (figure out a way to
    make it work with Bindings), but the problem is that this is pretty
    detailed design work and we're all flying blind. I'm sure this is
    solvable, but not without spending time looking at how your project
    is setup.

    Two things you can take away from all of this, though:

    1. The objects your return from indexed accessors don't have to be
    NSManagedObjects. They can just be regular objects.

    2. It's very common for a class to have the "real" data in an NSData
    blob, but to also have a secondary "cached" version stored as an
    NSString instance variable (or whatever you need). You only re-
    generated the secondary version when the first one changes.

    A good example of this is managed objects that have an color
    property. The real color value is an NSColor object serialized to an
    NSData object. The "cached" version is a transient property (which
    means, it's not stored in the database) which has an "undefined" type.

    Hope that helps.

        - Scott
  • OK.  I'll give it another try.  I wonder if it might
    be easier to subclass my NSArrayController, and see if
    I can clean things up in its subclass? - Philip

    --- Scott Stevenson <scott...> wrote:

    >
    > On Oct 14, 2006, at 10:48 AM, joe OneNinetyTwo
    > wrote:
    >
    >> I would love to stick with bindings, but it's
    > still
    >> not obvious how I can do it with a mutable "faked"
    >> array and KVC indexed accessors.  - Philip
    >
    > So you're technically doing what I suggested (figure
    > out a way to
    > make it work with Bindings), but the problem is that
    > this is pretty
    > detailed design work and we're all flying blind. I'm
    > sure this is
    > solvable, but not without spending time looking at
    > how your project
    > is setup.
    >
    > Two things you can take away from all of this,
    > though:
    >
    > 1. The objects your return from indexed accessors
    > don't have to be
    > NSManagedObjects. They can just be regular objects.
    >
    > 2. It's very common for a class to have the "real"
    > data in an NSData
    > blob, but to also have a secondary "cached" version
    > stored as an
    > NSString instance variable (or whatever you need).
    > You only re-
    > generated the secondary version when the first one
    > changes.
    >
    > A good example of this is managed objects that have
    > an color
    > property. The real color value is an NSColor object
    > serialized to an
    > NSData object. The "cached" version is a transient
    > property (which
    > means, it's not stored in the database) which has an
    > "undefined" type.
    >
    > Hope that helps.
    >
    > - Scott
    >
    >

    __________________________________________________
    Do You Yahoo!?
    Tired of spam?  Yahoo! Mail has the best spam protection around
    http://mail.yahoo.com
  • On Oct 14, 2006, at 8:48 PM, joe OneNinetyTwo wrote:

    > OK.  I'll give it another try.  I wonder if it might
    > be easier to subclass my NSArrayController, and see if
    > I can clean things up in its subclass? - Philip

    Do whatever works best, but I really think it's something to fix on
    the model side. As far as I can tell, Bindings already works fine for
    what you want to do.

        - Scott
  • I have a simple solution, and posted an example at...

    http://www.chemistry.ohio-state.edu/~grandinetti/KVC.zip

    The code gives a simple example to demonstrate how to
    use Key Value Coding and implement Indexed Accessors
    for a To-Many Property to display mutable data in a
    NSTableView using an NSArrayController and bindings,
    when the To-Many Property is packed into an NSData
    object.

    Hopefully, the code is self-explanatory.  In this
    approach a simple object class (Datum) is created to
    hold the table row values and an index.

    Inside my NSDocument subclass are the methods ...

    - (unsigned int) countOfData
    - (Datum *) objectInDataAtIndex:(unsigned int) index
    - (void) insertObject: (Datum *) datum
    inDataAtIndex:(unsigned int) index
    - (void) removeObjectFromDataAtIndex: (unsigned
    int)index

    which interact with the NSArrayController, through the
    bindings with myDocument, to act like a property
    called "data", which is indistinguishable from an
    mutable array.

    The trick here, is to use the Notification Center and
    have the Datum object post a notification when its
    value has changed (i.e., when the user edits a cell in
    the tableView).  The NSDocument instance is registered
    as an observer of the Datum notification, which calls
    its method handleDatumChange: to update the NSData
    object
    when one of its packed values needs to be updated.

    That's it.  Thanks to Scott Stevenson for encouraging
    me to find a simple solution using bindings.

    If anyone can see another solution that might be
    simpler, please let me know.

    Philip

    --- Scott Stevenson <scott...> wrote:
    > Do whatever works best, but I really think it's
    > something to fix on
    > the model side. As far as I can tell, Bindings
    > already works fine for
    > what you want to do.
    >
    > - Scott

    __________________________________________________
    Do You Yahoo!?
    Tired of spam?  Yahoo! Mail has the best spam protection around
    http://mail.yahoo.com
  • On Oct 15, 2006, at 3:41 PM, joe OneNinetyTwo wrote:

    > I have a simple solution, and posted an example at...
    >
    > http://www.chemistry.ohio-state.edu/~grandinetti/KVC.zip
    [...]
    > The code gives a simple example to demonstrate how to
    > use Key Value Coding and implement Indexed Accessors
    > for a To-Many Property to display mutable data in a
    > NSTableView using an NSArrayController and bindings,
    > when the To-Many Property is packed into an NSData
    > object.
    [...]
    > If anyone can see another solution that might be
    > simpler, please let me know.

    This solution is more than fine. The only downside is that you
    generate a lot of Datum objects that immediately go away.

    I think you could make things simpler and faster with an
    NSMutableArray which mirrors the contents NSData object. The idea is
    that you bind the UI to that array, and just rebuild the backing
    NSData object whenever the array changes (or maybe not even that often).

    Not only does this mean you get to throw away all of the psuedo-array
    code, but I imagine your loading and display would be more efficient
    without all of the auto release, and you get to leverage all of
    NSMutableArray's intensive optimizations.

    I can't be sure this design works for app, but based on the sample
    code you sent, I think it would.

    By the way, when you post a zip file with code, be sure to remove the
    "build" directory first. 52k versus 1.7MB.  :)

        - Scott
previous month october 2006 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