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 compliantNo 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 youAgain no. In the typical case, you *should* implement the indexed
> 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.
>
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 ifIt's not clear why you're making things more complicated than necessary.
> 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
>
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 symbolicconstant; 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 connectionsto 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 intoIt was once fairly common practice to use the C preprocessor (home of
> 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
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:you can define an external NSString as such:
>> #define ATTENDEES_KEY @"attendees"
>> above your implementation, along with your #import statements, or>> // 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 forrun-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


