KVC and KVO for arrays

  • I came along this problem because I understand the binding mechanism
    requires kvc calls  in order to get the observing going. It is unclear
    to me how to do this for changes in array. (I did read the KVC and KVO
    Programming Guides).

    Setting an array works, for example:

    NSMutableArray *myArray;
    @property(copy) NSMutableArray *myArray;
    @synthesize myArray;
    ....
    myArray = [NSMutableArray arrayWithCapacity:10];
    // fill myArray with objects for ex. with [myArray addObject:..] etc.
    [self setMyArray:myArray]

    In the above code the NSArrayController bound to myArray a Model Key
    Path gets informed of the change.

    In the documentation there is mention of "indexed accessor methods"
    that will directly inform the array of changes. For changes these are
    given as insertObject:in<Key>AtIndex: and removeObjectFrom<Key>AtIndex:
    The <key> in this case being MyArray.

    That sounds fine for implementing array accessing in a custom class of
    my own. But what for the standard NSArrayMutable array? When coding:
    [myArray insertObject:anObject inMyArrayAtIndex:0]
    the compiler warns me that NSMutableArray does not know this method
    and on executing I get the more or less expected error: [NSCFArray
    insertObject:inSolutionsAtIndex:]: unrecognized selector sent to
    instance.

    Is there something I do not see? Or do I have to add all the "indexed
    accessor methods"  to NSArray and NSMutableArray in a category?
    That generates another question: in that case, must I program for
    manual observing by bracketing the implementation statements with
    [self willChangeValueForKey ..etc] messages?

    Finally I tried:
    myArray = [[NSMutableArray arrayWithCapacity:10]
    mutableArrayValueForKey:@"myArray"];
    [myArray addObject:anObject];
    But that did not work either, the NSArrayController not being updated.
    Though no NSUndefinedKeyException was raised, so
    mutableArrayValueForKey must have had some effect.

    Could someone possibly enlighten me? Thanks in advance.

    Hans van der Meer
  • On 12 feb 2008, at 22:45, Kyle Sluder wrote:

    > On 2/12/08, Hans van der Meer <hansm...> wrote:
    >> Is there something I do not see? Or do I have to add all the "indexed
    >> accessor methods"  to NSArray and NSMutableArray in a category?
    >> That generates another question: in that case, must I program for
    >> manual observing by bracketing the implementation statements with
    >> [self willChangeValueForKey ..etc] messages?
    >
    > You seem to be confused... why would you be listening for changes to
    > custom properties on the array itself?
    >

    I guess so. My intention is to have the NSArrayController following
    the changes in the mutable array in order to show these changes in a
    tableview in the GUI. As I understand it, to accomplish this the
    NSArrayController should observe the changes in the array. Doing
    simply [array addObject:] apparently does not trigger the
    NSArrayController into having the tableview follow the change.

    Hans van der Meer
  • NSMutableArray is already KVC and KVO compliant, so you don't need to
    do anything special to use them with bindings.  You only need to
    implement indexed accessor methods if you're writing your own class
    which you'd like to behave like an array with regard to bindings.

    On Feb 12, 2008, at 3:32 PM, Hans van der Meer wrote:

    > I came along this problem because I understand the binding mechanism
    > requires kvc calls  in order to get the observing going. It is
    > unclear to me how to do this for changes in array. (I did read the
    > KVC and KVO Programming Guides).
    >
    > Setting an array works, for example:
    >
    > NSMutableArray *myArray;
    > @property(copy) NSMutableArray *myArray;
    > @synthesize myArray;
    > ....
    > myArray = [NSMutableArray arrayWithCapacity:10];
    > // fill myArray with objects for ex. with [myArray addObject:..] etc.
    > [self setMyArray:myArray]
    >
    > In the above code the NSArrayController bound to myArray a Model Key
    > Path gets informed of the change.
    >
    > In the documentation there is mention of "indexed accessor methods"
    > that will directly inform the array of changes. For changes these
    > are given as insertObject:in<Key>AtIndex: and
    > removeObjectFrom<Key>AtIndex:
    > The <key> in this case being MyArray.
    >
    > That sounds fine for implementing array accessing in a custom class
    > of my own. But what for the standard NSArrayMutable array? When
    > coding:
    > [myArray insertObject:anObject inMyArrayAtIndex:0]
    > the compiler warns me that NSMutableArray does not know this method
    > and on executing I get the more or less expected error: [NSCFArray
    > insertObject:inSolutionsAtIndex:]: unrecognized selector sent to
    > instance.
    >
    > Is there something I do not see? Or do I have to add all the
    > "indexed accessor methods"  to NSArray and NSMutableArray in a
    > category?
    > That generates another question: in that case, must I program for
    > manual observing by bracketing the implementation statements with
    > [self willChangeValueForKey ..etc] messages?
    >
    > Finally I tried:
    > myArray = [[NSMutableArray arrayWithCapacity:10]
    > mutableArrayValueForKey:@"myArray"];
    > [myArray addObject:anObject];
    > But that did not work either, the NSArrayController not being
    > updated. Though no NSUndefinedKeyException was raised, so
    > mutableArrayValueForKey must have had some effect.
    >
    > Could someone possibly enlighten me? Thanks in advance.
    >
    > Hans van der Meer
  • On Feb 12, 2008, at 2:09 PM, Adam P Jenkins wrote:

    > NSMutableArray is already KVC and KVO compliant
    >
    No it isn't.

    <http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaBindings/Con
    cepts/Troubleshooting.html#//apple_ref/doc/uid/TP40002148-182809
    >

    mmalc
  • Ah, you're correct.  But my basic point still is correct, that you do
    not need to subclass NSMutableArray or implement indexed accessors
    yourself.  You can just use the mutableArray* and mutableSet* methods
    to have Cocoa do the work for you.

    Adam

    On Feb 13, 2008, at 8:00 AM, mmalc crawford wrote:

    >
    > On Feb 12, 2008, at 2:09 PM, Adam P Jenkins wrote:
    >
    >> NSMutableArray is already KVC and KVO compliant
    >>
    > No it isn't.
    >
    > <http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaBindings/Con
    cepts/Troubleshooting.html#//apple_ref/doc/uid/TP40002148-182809
    > >
    >
    > mmalc
    >
  • On Feb 13, 2008, at 6:37 AM, Adam P Jenkins wrote:

    > Ah, you're correct.  But my basic point still is correct, that you
    > do not need to subclass NSMutableArray or implement indexed
    > accessors yourself.  You can just use the mutableArray* and
    > mutableSet* methods to have Cocoa do the work for you.
    >
    Again no.  In the typical case, you *should* implement the indexed
    accessors, otherwise you end up replacing the whole array/set each
    time you make a change, which is extremely inefficient.

    mmalc
  • On Feb 13, 2008, at 10:34 AM, mmalc crawford wrote:

    >
    > On Feb 13, 2008, at 6:37 AM, Adam P Jenkins wrote:
    >
    >> Ah, you're correct.  But my basic point still is correct, that you
    >> do not need to subclass NSMutableArray or implement indexed
    >> accessors yourself.  You can just use the mutableArray* and
    >> mutableSet* methods to have Cocoa do the work for you.
    >>
    > Again no.  In the typical case, you *should* implement the indexed
    > accessors, otherwise you end up replacing the whole array/set each
    > time you make a change, which is extremely inefficient.

    Thanks.  You're correct, depending on how you write your entity
    class.  If your entity class does NOT provide indexed accessor methods
    for an attribute, but DOES provide a set<Key> method for the array
    attribute, then the array proxy returned by mutableArrayValueForKey
    does behave the way you said, replacing the whole array any time an
    element is changed.

    However if your entity class doesn't provide a set<Key> method, but
    DOES provide an instance variable named <key> whose value is an
    NSMutableArray, then the proxy returned by mutableArrayValueForKey
    will forward array modification messages directly to the underlying
    NSMutableArray, while also sending the appropriate key/value observing
    notifications.  See the documentation for the mutableArrayValueForKey:
    method of NSKeyValueCoding for details.

    So basically, an easy way to get the effect of having indexed accessor
    methods without having to write them is as follows:

    @interface Palette : NSObject
    {
      NSMutableArray *colors;
    }

    // don't define a colors property or setColors: method

    // returns a KVO compliant colors array
    - (NSMutableArray*)kvoColors;
    @end

    @implementation Palette
    - (NSMutableArray*)kvoColors
    {
      return [self mutableArrayValueForKey:@"colors"];
    }
    @end

    Now, I can write
    [[palette kvoColors] addObject:[NSColor whiteColor]];
    and observers of palette will be duly notified.
  • On 13 Feb '08, at 8:51 AM, Adam P Jenkins wrote:

    > @implementation Palette
    > - (NSMutableArray*)kvoColors
    > {
    > return [self mutableArrayValueForKey:@"colors"];
    > }
    > @end
    >
    > Now, I can write
    > [[palette kvoColors] addObject:[NSColor whiteColor]];
    > and observers of palette will be duly notified.

    This has always confused me, and I never got it to work right when I
    tried this technique. The weird part is that you have two KV
    properties for the same array, with different semantics. Your trick of
    declaring a "colors" ivar but no accessor methods looks like it helps,
    but one thing still worries me:

    If I made that addObject call, as in your example, what KVO
    notifications does the Palette send? Is it the "kvoColors" or "colors"
    property that's notified as being changed?

    I'm afraid that it would be the latter, which makes me somewhat afraid
    to use this technique, since I'd be too likely to get mixed up and
    register for notifications of "kvoColors" instead of "colors", and
    then spend hours trying to figure out why my listener doesn't get
    called...

    —Jens
  • On Feb 13, 2008, at 12:08 PM, Jens Alfke wrote:

    >
    > On 13 Feb '08, at 8:51 AM, Adam P Jenkins wrote:
    >
    >> @implementation Palette
    >> - (NSMutableArray*)kvoColors
    >> {
    >> return [self mutableArrayValueForKey:@"colors"];
    >> }
    >> @end
    >>
    >> Now, I can write
    >> [[palette kvoColors] addObject:[NSColor whiteColor]];
    >> and observers of palette will be duly notified.
    >
    > This has always confused me, and I never got it to work right when I
    > tried this technique. The weird part is that you have two KV
    > properties for the same array, with different semantics. Your trick
    > of declaring a "colors" ivar but no accessor methods looks like it
    > helps, but one thing still worries me:
    >
    > If I made that addObject call, as in your example, what KVO
    > notifications does the Palette send? Is it the "kvoColors" or
    > "colors" property that's notified as being changed?
    >
    > I'm afraid that it would be the latter, which makes me somewhat
    > afraid to use this technique, since I'd be too likely to get mixed
    > up and register for notifications of "kvoColors" instead of
    > "colors", and then spend hours trying to figure out why my listener
    > doesn't get called...
    >
    > —Jens

    The kvoColors method is not necessary, and you can leave it out if you
    think it confuses things.  I just added it for illustrative purposes.
    By default, simply adding an instance variable is enough to create a
    KVC and KVO compliant property, unless a subclass has overridden
    accessInstanceVariablesDirectly to return FALSE.  Here is a complete
    and compilable program which demonstrates a Party class with an
    attendees array property, and demonstrates an observer receiving
    events about the array being modified in place, all without writing
    any indexed accessors.

    #import <Foundation/Foundation.h>

    @interface Party : NSObject
    {
    // just adding this instance variable is enough to create a KVC and
    KVO compliant
        // attendees property
    NSMutableArray *attendees;
    }

    - (void)printAttendeesAddress;
    @end

    @implementation Party
    - (id)init {
    [super init];
    attendees = [NSMutableArray array];
    return self;
    }

    - (void)printAttendeesAddress {
    NSLog(@"Attendees array is at address %p", attendees);
    }
    @end

    @interface MyObserver : NSObject
    @end

    @implementation MyObserver
    - (void)observeValueForKeyPath:(NSString *)keyPath
                         ofObject:(id)object
                            change:(NSDictionary *)change
                            context:(void *)context
    {
    NSLog(@"Received notification: keyPath:%@ ofObject:%@ change:%@",
    keyPath, object, change);
    }
    @end

    int main (int argc, const char * argv[]) {
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    Party *party = [Party new];
    MyObserver *observer = [MyObserver new];

    // observe attendees property
    [party addObserver:observer
      forKeyPath:@"attendees"
                 options:(NSKeyValueObservingOptionNew |
    NSKeyValueObservingOptionOld)
                 context:NULL];

    [party printAttendeesAddress];

            // modifications to attendees will be logged by observer
    NSMutableArray *attendees = [party
    mutableArrayValueForKey:@"attendees"];
    [attendees addObject:@"Joe Blow"];
    [attendees addObject:@"Mary Jane"];
    [attendees replaceObjectAtIndex:0 withObject:@"Spiderman"];
    [attendees removeObjectAtIndex:1];

    // this should print the same address as the previous invocation of
    this method
    // showing that the instance variable's underlying array isn't
    getting copied
    [party printAttendeesAddress];

        [pool drain];
        return 0;
    }
  • On Feb 13, 2008, at 8:51 AM, Adam P Jenkins wrote:

    > So basically, an easy way to get the effect of having indexed
    > accessor methods without having to write them is as follows:

    Please, read what I wrote.
    No you don't.

    The collection proxy still has to replace the whole array rather than
    just mutate it, so it's inefficient.

    mmalc
  • On Feb 13, 2008, at 11:24 AM, Adam P Jenkins wrote:

    > The kvoColors method is not necessary, and you can leave it out if
    > you think it confuses things.  I just added it for illustrative
    > purposes.  By default, simply adding an instance variable is enough
    > to create a KVC and KVO compliant property, unless a subclass has
    > overridden accessInstanceVariablesDirectly to return FALSE.  Here
    > is a complete and compilable program which demonstrates a Party
    > class with an attendees array property, and demonstrates an observer
    > receiving events about the array being modified in place, all
    > without writing any indexed accessors.
    >
    > #import <Foundation/Foundation.h>
    >
    > @interface Party : NSObject
    > {
    > // just adding this instance variable is enough to create a KVC and
    > KVO compliant
    > // attendees property
    > NSMutableArray *attendees;
    > }
    >
    > - (void)printAttendeesAddress;
    > @end
    >
    > @implementation Party
    > - (id)init {
    > [super init];
    > attendees = [NSMutableArray array];
    > return self;
    > }
    >
    > - (void)printAttendeesAddress {
    > NSLog(@"Attendees array is at address %p", attendees);
    > }
    > @end
    >
    It's not clear why you're making things more complicated than necessary.
    Just uses indexed accessors.

    The above will not work unless you're using garbage collection...

    mmalc
  • On Feb 13, 2008 4:21 PM, mmalc crawford <mmalc_lists...> wrote:
    > The collection proxy still has to replace the whole array rather than
    > just mutate it, so it's inefficient.

    To be fair, you have not provided any supporting evidence of your
    claim.  Granted, the behavior you describe has always been my
    suspicion of how array proxies work, but then again, what Adam
    describes also makes a lot of sense.  If the KVO array proxy knows
    that it's being used as a proxy for an NSMutableArray ivar, then it
    can forward its messages to the ivar and then dispatch appropriate KVO
    notifications rather than construct a new array and call -set<Key>:
    with the new array.

    --Kyle Sluder
  • On Feb 13, 2008, at 4:30 PM, mmalc crawford wrote:
    > It's not clear why you're making things more complicated than
    > necessary.
    > Just uses indexed accessors.
    >
    > The above will not work unless you're using garbage collection...

    I don't think my example is complicated at all.  In fact leaving out
    the indexed accessors makes my Party class trivially simple.  I do
    agree that in a production app I'd probably just bite the bullet and
    add indexed accessors.  I'm just demonstrating that you don't need to
    implement them, because the array proxy that mutableArrayValueForKey:
    returns will do the work for you if you follow the right conventions.
    It is a little fragile, because if you later added accessor methods to
    the Party class for the attendees property, it would revert to copying
    the array on each modification.

    As for your GC comment, you're probably right that my code leaks
    without GC turned on.  Since I got Leopard, I've gotten into the
    habit of just enabling GC for all my projects, and haven't looked
    back.  I do realize this makes my programs unable to run on previous
    versions of OSX, and that if I was writing commercial Mac software I
    might not make that choice.  But I enjoy programming much more when
    the language I'm using has GC, and since my Mac programming is all
    just hobby stuff I don't mind if it won't run on pre-Leopard systems.

    Adam
  • On Feb 12, 2008, at 2:32 PM, Hans van der Meer wrote:

    > I came along this problem because I understand the binding mechanism
    > requires kvc calls  in order to get the observing going. It is
    > unclear to me how to do this for changes in array. (I did read the
    > KVC and KVO Programming Guides).
    >
    > Setting an array works, for example:
    >
    > NSMutableArray *myArray;
    > @property(copy) NSMutableArray *myArray;
    > @synthesize myArray;
    > ....
    > myArray = [NSMutableArray arrayWithCapacity:10];
    > // fill myArray with objects for ex. with [myArray addObject:..] etc.
    > [self setMyArray:myArray]
    >
    > In the above code the NSArrayController bound to myArray a Model Key
    > Path gets informed of the change.
    >
    > In the documentation there is mention of "indexed accessor methods"
    > that will directly inform the array of changes. For changes these
    > are given as insertObject:in<Key>AtIndex: and
    > removeObjectFrom<Key>AtIndex:
    > The <key> in this case being MyArray.

    Generally, for the array KVC/KVO messages, <key> is a plural noun.
    For example, the <key> might be "widgets", so the template method name
    above becomes:

    insertObject:inWidgetsAtIndex:

    >
    >
    > That sounds fine for implementing array accessing in a custom class
    > of my own. But what for the standard NSArrayMutable array? When
    > coding:
    > [myArray insertObject:anObject inMyArrayAtIndex:0]
    > the compiler warns me that NSMutableArray does not know this method
    > and on executing I get the more or less expected error: [NSCFArray
    > insertObject:inSolutionsAtIndex:]: unrecognized selector sent to
    > instance.

    The fundamental confusion is that you don't want to (and can't) make a
    _property_ KVC/KVO compliant.  You want to make your _class_ KVC/KVO
    compliant _for_ a property.  It doesn't make any sense to think about
    a mutable array being KVO compliant for, uh, it's contents.  You need
    to make a class, which has a mutable array property, KVO-compliant for
    that property.

    So, the method insertObject:in<Key>AtIndex: is not something you'd
    invoke on the mutable array.  It's something you'd invoke on the
    object of the class with the property.  The object, on receiving that
    message, would do whatever it wants to keep track of the change
    internally, and the KVO mechanism would automatically send out the
    proper notifications.

    So, you'd invoke:

    [self insertObject:anObject inWidgetsAtIndex:0];

    Honestly, I don't know if Objective-C 2.0's new property stuff is
    smart enough to synthesize array mutation accessors.  If not, you'd
    manually implement the following on your class (not on NSMutableArray
    via a cateogory!):

    - (void)insertObject:(MyElementClass*)anObject inWidgetsAtIndex:
    (unsigned int)index
    {
    [widgets insertObject:anObject atIndex:index];
    }

    Note where I said "would do whatever it wants to keep track of the
    change internally", above.  In my code snippet above, I assumed that
    the class has an ivar "widgets" which is an NSMutableArray*.  This is
    not required by KVC, KVO, or anything.  A class may back a property
    with whatever implementation it wants.  It's even possible for a class
    to have a property with no backing storage, at all -- the class could
    synthesize the value for the property dynamically.

    This is an important concept to grasp, because if you understood it
    you wouldn't have been tempted to make the ivar KVO-compliant.  KVO is
    concerned with the properties (a.k.a. keys) of the class, not with the
    implementation of their backing storage.  (Yes, KVC and KVO will fall
    back to direct access to ivars if necessary, but that's not their
    purpose.)

    > do I have to add all the "indexed accessor methods"  to NSArray and
    > NSMutableArray in a category?

    Definitely no.

    >
    > That generates another question: in that case, must I program for
    > manual observing by bracketing the implementation statements with
    > [self willChangeValueForKey ..etc] messages?

    You can do that, instead, if you prefer.  However, you should use the
    will/didChange:valuesAtIndexes:forKey: form when mutating arrays, for
    efficiency.  Just using willChangeValueForKey: tells observers that
    you've replaced the property's value wholesale.  The other form allows
    KVO to inform observers of just the specific changes you've made.

    Another alternative is to obtain a proxy object for your mutable array
    property using [someObject mutableArrayValueForKey:@"widgets"], where
    someObject is an object of a class with a "widgets" property.  When
    invoked from within that class, you can use self.  Any mutation
    operations you perform on the proxy are performed in a KVO-compliant
    manner.  If your class has existing KVO-compliant mutating accessors
    as described above, the proxy will use those.  If it doesn't, the
    proxy may call set<Key>:, which is inefficient.  Otherwise, it will
    directly manipulate your ivar, and perform the will/didChange:...
    calls itself.

    >
    >
    > Finally I tried:
    > myArray = [[NSMutableArray arrayWithCapacity:10]
    > mutableArrayValueForKey:@"myArray"];
    > [myArray addObject:anObject];
    > But that did not work either, the NSArrayController not being
    > updated. Though no NSUndefinedKeyException was raised, so
    > mutableArrayValueForKey must have had some effect.

    The above is very confused.  First, you're creating a new empty
    mutable array which has no relation to anything.  Even if the rest of
    the code were conceptually sound, you'd be accessing an object having
    nothing to do with anything else.  For example, who could possibly be
    observing properties of that array?

    Second, you ask this new array for one of its properties, named
    "myArray".  Now, NSMutableArray doesn't have any such property.

    Again, what you mean to be doing is asking the object of your own
    class for its "myArray" property (or, rather, a KVO proxy for that
    property as described above).  When coding that class, you would
    obtain it with:

    [self mutableArrayValueForKey:@"myArray"]

    However, using the proxy in this case is just lazy.  Since you're
    coding the class, you should add the proper array mutation primitives
    and invoke those.

    > Could someone possibly enlighten me? Thanks in advance.

    I hope that helps.

    Cheers,
    Ken
  • On 13 Feb '08, at 11:24 AM, Adam P Jenkins wrote:

    > The kvoColors method is not necessary, and you can leave it out if
    > you think it confuses things.

    Yeah, but then clients of your class have to access your property with
    really ugly calls like
    NSMutableArray *attendees = [party
    mutableArrayValueForKey:@"attendees"];
    This is really bad form, IMHO, because it breaks both object
    encapsulation and compile-time method checking:

    (1) There's nothing in the Party class that indicates "attendees" is
    public. I could use the same technique to access any private instance
    variable of a class, with neither compiler nor runtime warnings. The
    "valueForKey" family of messages remind me of the old PEEK and POKE in
    BASIC.

    (2) If I misspell the key as "atendees", I won't get any compile-time
    warning, just an exception at runtime.

    (3) If the instance variable 'party' is by mistake typed as, say
    Person* instead of Party*, I won't get a compile-time warning about
    Person not having an "attendees" property.

    (4) This is also 26 more characters to type, and much harder to read,
    than "[party attendees]".

    To some degree this is a matter of taste, but if I didn't care about
    type-checking or data encapsulation, I'd be coding in Python
    instead :) (and save a lot more than 26 characters; Python is hella
    compact compared to Obj-C.)

    —Jens
  • I agree with everything you said below regarding static type checking,
    and the advantages of getting the compiler to do as much checking as
    possible for you.  For work I do a lot of Java, C++, and Python
    programming, so I do have some other languages to compare ObjC to.

    I've just come to accept that when using ObjC and Cocoa especially,
    there are a lot of potential errors that don't get caught until
    runtime even if I do try to use accessor methods and static types as
    much as possible.  For instance there are no templates or generics in
    ObjC so container classes like arrays and sets can contain different
    types of objects than you expect.  There are many methods throughout
    the Cocoa APIs which just return id.  When using bindings, or
    creating GUIs with Interface Builder you're forced to use strings to
    refer to properties all the time whether or not you've defined
    accessors.  It's very easy to end up with incorrect names in a binding
    or connection, and the error messages you get are no more informative
    if you've defined an accessor method than if you haven't.  And unlike
    Python or Java, the ObjC runtime doesn't give me any nice stack trace
    showing me where the problem occurred; I just get a message in the
    console.log or system.log saying that an attempt was made to access a
    non-existent property.  If you've ever spent an hour poring over all
    the bindings and connections in Interface Builder trying to spot the
    misspelling that's causing a problem, you'll know what I mean.

    So the upshot of my little rant is that it doesn't always seem that
    useful to me to create accessor methods in ObjC if I don't have to,
    like it would be in Java or C++.  It really depends on how the class
    will be used.  If I'm going to be writing a lot of code by hand which
    uses the class then it's certainly worth adding accessors.  If I'm
    mainly going to be using the class with controllers and bindings, then
    it doesn't seem to buy me much to add accessors if the default KVC
    methods do what I want, since the way bindings work in Cocoa doesn't
    take any advantage of static type checking.

    To use my Party class as an example, if I was going to be writing a
    lot of code which used the Party class, then I'd certainly rather not
    have to write [party mutableArrayValueForKey:@"attendees"] too many
    times.  On the other hand, if the Party class was mainly going to be
    used just as the model behind a GUI which let you edit the object, and
    I just needed to bind the attendees property to an array controller,
    then there wouldn't be much advantage to me writing my own accessor
    methods compared to just using the functionality provided by
    mutableArrayValueForKey:.  Either way I have to specify the binding to
    the attendees property as a string, and if I spell it wrong I won't
    get notified until runtime.

    Adam

    On Feb 13, 2008, at 6:33 PM, Jens Alfke wrote:

    >
    > On 13 Feb '08, at 11:24 AM, Adam P Jenkins wrote:
    >
    >> The kvoColors method is not necessary, and you can leave it out if
    >> you think it confuses things.
    >
    > Yeah, but then clients of your class have to access your property
    > with really ugly calls like
    > NSMutableArray *attendees = [party
    > mutableArrayValueForKey:@"attendees"];
    > This is really bad form, IMHO, because it breaks both object
    > encapsulation and compile-time method checking:
    >
    > (1) There's nothing in the Party class that indicates "attendees" is
    > public. I could use the same technique to access any private
    > instance variable of a class, with neither compiler nor runtime
    > warnings. The "valueForKey" family of messages remind me of the old
    > PEEK and POKE in BASIC.
    >
    > (2) If I misspell the key as "atendees", I won't get any compile-
    > time warning, just an exception at runtime.
    >
    > (3) If the instance variable 'party' is by mistake typed as, say
    > Person* instead of Party*, I won't get a compile-time warning about
    > Person not having an "attendees" property.
    >
    > (4) This is also 26 more characters to type, and much harder to
    > read, than "[party attendees]".
    >
    > To some degree this is a matter of taste, but if I didn't care about
    > type-checking or data encapsulation, I'd be coding in Python
    > instead :) (and save a lot more than 26 characters; Python is hella
    > compact compared to Obj-C.)
    >
    > —Jens
  • > (2) If I misspell the key as "atendees", I won't get any compile-time

    > warning, just an exception at runtime.

    This is an easy one to fix.  Just add:

    #define ATTENDEES_KEY @"attendees"

    above your  implementation, along with your #import statements, or you
    can define an external NSString as such:

    // myclass.h

    extern NSString *AttendeesKey;

    //myclass.m

    NSString *AttendeesKey = @"attendees";

    Then in you code, calls like

    [self setValue:anObject forKey:ATTENDEES_KEY]; // case 1

    or

    [self setValue:anObject forKey:AttendeesKey]; // case 2

    will both get compile-time checked and properly syntax highlighted.
    This has the added bonus of not having to do a find-replace on your
    whole code if you change the name of an ivar, and if you've defined the
    string wrongly then you don't have far to look to find your spelling
    mistake

    I'm not massively experienced in C, started in FORTRAN and came into
    Objective-C this year, can anyone tell me how the two versions differ
    and if adopting one over the other will have any effect on my code
    later?  Much of the Apple code I've seen use #define statements, but
    I've seen the NSString elsewhere like in Hillegass's book.

    Jon

    This e-mail and any attachments may contain confidential and
    privileged information. If you are not the intended recipient,
    please notify the sender immediately by return e-mail, delete this
    e-mail and destroy any copies. Any dissemination or use of this
    information by a person other than the intended recipient is
    unauthorized and may be illegal.

    Please be aware that, under the terms of the Freedom of
    Information Act 2000, Swansea NHS Trust may be required to make
    public the content of any emails or correspondence received.  For
    further information on Freedom of Information, please refer to the
    Swansea NHS Trust website at www.swansea-tr.wales.nhs.uk
  • I don't know what you mean by "effect" on your code.

    #define (in this case) is a preprocessing directive for a symbolic
    constant; this is processed before the remaining source code and
    cannot change.

    Assuming that you want to define a constant, you can use the const
    qualifier e.g. const double e = 2.71828182845905;

    The other version (extern NSString *attendeesKey;) is an external
    variable declaration (not a constant). I don't think that it is a very
    good idea to use external variables (especially in object-oriented
    programming).

    Nick

    On 14 Φεβ 2008, at 4:05 ΜΜ, John Dann wrote:

    > I'm not massively experienced in C, started in FORTRAN and came into
    > Objective-C this year, can anyone tell me how the two versions differ
    > and if adopting one over the other will have any effect on my code
    > later?  Much of the Apple code I've seen use #define statements, but
    > I've seen the NSString elsewhere like in Hillegass's book.
    >
    > Jon
  • > I don't know what you mean by "effect" on your code.

    Ok, sorry, I mean could this come back and bite me if I need to
    refactor my code in some way?  I was wondering if there was a subtle
    difference in the way #defines and external variables operate that could
    cause headaches

    > #define (in this case) is a preprocessing directive for a symbolic
    constant; this is processed before the remaining source code and cannot
    change.

    <snip>

    > I don't think that it is a very good idea to use external variables
    (especially in object-oriented  programming).

    I think you've answered it here though, do you think its worse to use
    externals as I could unintentionally reassign them during excecution?

    Thanks for your reply, discussions like this I can't get anywhere
    else!

    Jon

    This e-mail and any attachments may contain confidential and
    privileged information. If you are not the intended recipient,
    please notify the sender immediately by return e-mail, delete this
    e-mail and destroy any copies. Any dissemination or use of this
    information by a person other than the intended recipient is
    unauthorized and may be illegal.

    Please be aware that, under the terms of the Freedom of
    Information Act 2000, Swansea NHS Trust may be required to make
    public the content of any emails or correspondence received.  For
    further information on Freedom of Information, please refer to the
    Swansea NHS Trust website at www.swansea-tr.wales.nhs.uk
  • On 15 Φεβ 2008, at 12:27 ΜΜ, John Dann wrote:

    >> I don't know what you mean by "effect" on your code.
    >
    > Ok, sorry, I mean could this come back and bite me if I need to
    > refactor my code in some way?  I was wondering if there was a subtle
    > difference in the way #defines and external variables operate that
    > could
    > cause headaches

    I can't think of any reason why any of these should cause a problem.

    >> #define (in this case) is a preprocessing directive for a symbolic
    >> constant; this is processed before the remaining source code and
    >> cannot
    >> change.
    >
    > <snip>
    >
    >> I don't think that it is a very good idea to use external variables
    >> (especially in object-oriented  programming).
    >
    > I think you've answered it here though, do you think its worse to use
    > externals as I could unintentionally reassign them during execution?

    Exactly. That could be a problem, depending on how big your program is
    and whether there are inadvertent changes introduced in it. I was also
    referring to the problem of ending up with data connections to various
    places, that will not all be apparent when your program becomes
    sizable. It is also a bad practice in terms of object-orientation as
    your objects are best when they are fully encapsulated.

    Nick
  • When I started this chain the problem was something like:

    @interface ... { IBOutlet NSMutableArray *myArray; }
    @property(readonly) IBOutlet NSMutableArray *myArray;    // ignore
    warning on assign
    @implementation
    @synthesize myArray;
    in the init method: myArray = [NSMutableArray arrayWithCapacity:20];
    in an action method: [myArray addObject:anObject];
    myArray with IB bound through an NSArrayController to a tableview in
    the GUI.

    I then observed that the addObject did not lead to changes in the GUI:
    the KVO mechanism didn't activate.
    My thanks to the many people who participated in the discussion. I
    followed them with interest and it stimulated me to keep looking into
    this problem from other angles.

    Finally some light in the tunnel. Reading the documentation on KVC-KVO
    may sometimes be tedious, it can be rewarding. After delving in
    indexed accessors and mutableArrayValueForKey, I stumbled at last on
    manually forcing KVO. It is in the Key-Value Observing Programming
    Guide (my version dated 2006-06-28) where on page 15 is said: "Using
    automatic observer notifications, it is not necessary to bracket
    changes to a property with invocations of willChangeValueForKey: and
    didChangeValueForKey:.
    Thus where arrays changed in contents did not give an automatic
    observer notification one could try these.

    So I then coded in the action method:

    [self willChangeValueForKey:@"myArray"];
      [myArray removeAllObjects];
      for (...) { ... [myArray addObject:anObject]; ...}
    [self didChangeValueForKey:@"myArray"];

    And now the GUI updates! Eureka!

    Hans van der Meer
  • > I was also referring to the problem of ending up with data connections
    to various
    > places, that will not all be apparent when your program becomes
    > sizable. It is also a bad practice in terms of object-orientation as

    > your objects are best when they are fully encapsulated.

    That's great, I appreciate your time.

    Jon

    This e-mail and any attachments may contain confidential and
    privileged information. If you are not the intended recipient,
    please notify the sender immediately by return e-mail, delete this
    e-mail and destroy any copies. Any dissemination or use of this
    information by a person other than the intended recipient is
    unauthorized and may be illegal.

    Please be aware that, under the terms of the Freedom of
    Information Act 2000, Swansea NHS Trust may be required to make
    public the content of any emails or correspondence received.  For
    further information on Freedom of Information, please refer to the
    Swansea NHS Trust website at www.swansea-tr.wales.nhs.uk
  • > I'm not massively experienced in C, started in FORTRAN and came into
    > Objective-C this year, can anyone tell me how the two versions differ
    > and if adopting one over the other will have any effect on my code
    > later?  Much of the Apple code I've seen use #define statements, but
    > I've seen the NSString elsewhere like in Hillegass's book.
    >
    > Jon
    It was once fairly common practice to use the C preprocessor (home of
    the #define and friends) on Fortran source code, then run the results
    through the Fortran compiler.

    Seeing Fortran with C preprocessor and macro definitions embedded was
    (at first) jarring, but it all works nicely.

    I mention this to flag the internal sequencing here.

    At its simplest, the preprocessor #define is for use by the programmer
    on the program source code and for the program source code itself, and
    prior to compilation, while NSString and C declarations are for run-time
    and external and display use.

    One wrinkle: various debuggers can't deal with and can't symbolize the C
    preprocessor names, just the resulting substitutions.  This again
    because the pre-processing here takes place upstream from the
    compilation, and its associated creation of the debug symbol tables.
  • >> This is an easy one to fix.  Just add:
    >> #define ATTENDEES_KEY @"attendees"
    >> above your  implementation, along with your #import statements, or
    you can define an external NSString as such:
    >> // myclass.h
    >> extern NSString *AttendeesKey;
    >> //myclass.m
    >> NSString *AttendeesKey = @"attendees";
    >> Then in you code, calls like
    >> [self setValue:anObject forKey:ATTENDEES_KEY]; // case 1
    >> or
    >> [self setValue:anObject forKey:AttendeesKey]; // case 2
    >> will both get compile-time checked and properly syntax highlighted.
    >
    > The other version (extern NSString *attendeesKey;) is an external
    > variable declaration (not a constant). I don't think that it is a very

    > good idea to use external variables (especially in object-oriented
    > programming).

    >> Much of the Apple code I've seen use #define statements, but
    >> I've seen the NSString elsewhere like in Hillegass's book.
    >
    > At its simplest, the preprocessor #define is for use by the programmer

    > on the program source code and for the program source code itself, and

    > prior to compilation, while NSString and C declarations are for
    run-time
    > and external and display use.

    Thanks for the input, I appreciate it a lot.

    In my case I'm now replacing the external NSString declarations with
    #defines, but this means in my implementation files where I place
    catergories on my own classes, which I've made to make my sourec easier
    and to split up the logic, I have to use use #define again for a few of
    my strings. For example I have my own NSDocument subclass and a category
    just for handling an NSTask-related methods, both of which have a

    #define SOURCE_LIST_COLUMN_ID @"sourceListColumn"

    in their implementation files.  If I want to change the name of the
    NSOutlineView column to which the #define statement referrs at some time
    in the future, I'll have to change both, and likely forget one.  Is
    there any way around this without placing the #define in my NSDocument
    subclass's .h file?

    Jon

    This e-mail and any attachments may contain confidential and
    privileged information. If you are not the intended recipient,
    please notify the sender immediately by return e-mail, delete this
    e-mail and destroy any copies. Any dissemination or use of this
    information by a person other than the intended recipient is
    unauthorized and may be illegal.

    Please be aware that, under the terms of the Freedom of
    Information Act 2000, Swansea NHS Trust may be required to make
    public the content of any emails or correspondence received.  For
    further information on Freedom of Information, please refer to the
    Swansea NHS Trust website at www.swansea-tr.wales.nhs.uk
  • On Fri, Feb 15, 2008 at 5:27 AM, John Dann
    <John.Dann...> wrote:
    > Ok, sorry, I mean could this come back and bite me if I need to
    > refactor my code in some way?  I was wondering if there was a subtle
    > difference in the way #defines and external variables operate that could
    > cause headaches

    Yes.  If you #define a constant NSString, there is no guarantee that
    the compiler will coalesce this constant with other identical NSString
    constants in other compilation units, meaning you can't do things like
    if(param == MyStringConstant).  For example:

    // file: MyErrors.h
    #define MY_ERROR_DOMAIN @"MyErrorDomain"
    extern NSString *MyErrorDomain;  // defined in MyErrors.m

    // file: MyObject.m
    #import "MyErrors.h"

    - (BOOL)performSomeActionWithError:(NSError *)err
    {
        if(err != NULL)
            *err = [NSError errorWithDomain:MY_ERROR_DOMAIN
                code:0 userInfo:nil];

        return NO;
    }

    // file: MyObjectClient.m
    #import "MyObject.h"
    #import "MyErrors.h"

    - (void)doSomething
    {
        MyObject *foo = [[MyObject alloc] init];
        NSError *err;
        if([foo performSomeActionWithError:&err] == NO)
        {
            // This is not guaranteed to work!
            // The constant created when MyObject.m is compiled
            // may be distinct from the one created when this file
            // is compiled.
            if([err domain] == MY_ERROR_DOMAIN])
                // do something
        }
    }

    -- Kyle Sluder
  • On 15 Φεβ 2008, at 5:52 ΜΜ, John Dann wrote:

    > If I want to change the name of the NSOutlineView column to which
    > the #define statement referrs at some time in the future, I'll have
    > to change both, and likely forget one.  Is there any way around this
    > without placing the #define in my NSDocument subclass's .h file?

    You could define a constant method (like in SmallTalk) in your main
    class, that your categories can call e.g.:

    - (NSString *) sourceListColumnID {
    return @"sourceListColumn";
    }

    This can be called from your categories and you won't have to
    duplicate your constants.

    Nick
  • > On 15 Φεβ 2008, at 5:52 ΜΜ, John Dann wrote:
    >
    >> If I want to change the name of the NSOutlineView column to which
    >> the #define statement referrs at some time in the future, I'll have
    >> to change both, and likely forget one.  Is there any way around
    >> this without placing the #define in my NSDocument subclass's .h file?
    >
    > You could define a constant method (like in SmallTalk) in your main
    > class, that your categories can call e.g.:
    >
    > - (NSString *) sourceListColumnID {
    > return @"sourceListColumn";
    > }
    >
    > This can be called from your categories and you won't have to
    > duplicate your constants

    Of course!  Isn't it embarrassing when one can't see the wood for the
    trees!

    Thanks again!

    Jon
previous month february 2008 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    
Go to today
MindNode
MindNode offered a free license !