views:

672

answers:

2

I'd like to test whether an object has a writeable @property in the iPhone SDK.

One possible way of doing this is to check the -valueForKey: method, but that seems rather inelegant!

Example:

  @try {
    id *value = [instance valueForKey:@"myProperty"];
  }
  @catch (NSException * e) {
    // Key did not exist
  }

Is there a better way of doing this?

+5  A: 

If you are creating the object that is being checked, you could override valueForUndefinedKey: and setValue:forUndefinedKey to do something more useful than raising an exception.

If, on the other hand, you are trying to introspect objects you don't know about at runtime, you will have to use the runtime methods to do that. You can either use the objective-c runtime itself and call either class_copyPropertyList or protocol_copyPropertyList and deal with those, or use Foundation and call respondsToSelector on the object for the KVC getter/setters for a given property, e.g., for a property foo you would call something like [someObject respondsToSelector:NSSelectorFromString(@"foo")];.

Jason Coco
+1  A: 

There is no way, in general, to determine if an object has a value for a given key. An instance may decide to return a value for an otherwise undefined key from its -valueForUndefinedKey: method. Or it may let the default implementation throw an exception. Modern Objective-C (2.0) objects often declare relevant properties in their public API. For these objects, you can use the Objective-C runtime's class_copyPropertyList or protocol_copyPropertyList to get a list of the available properties. You can also emulate the KVC search list, testing via repondsToSelector: if the instance responds to an appropriate getter method. Finally you could use the runtime's class_copyIvarList to check the ivars for an appropriate ivar. This is a lot of work that you shouldn't be doing. It won't guarantee that you know whether an object instance will raise an exception when sent a valueForKey: message and it indicates a bigger issue...

If you have a collection of objects that must all provide a value for a given key, but some don't, you have a design problem. You should define an appropriate interface (a @protocol in Objective-C) that declares the needed properties. You can then test for conformance to this protocol, or make use of the compiler to do some compile-time type checking for you to make sure instance conform to the protocol (if you're passing around single instances).

Barry Wark
Perhaps it would help to clarify!The application runs on multiple runtimes, with different versions of libraries available. In one version, a property I need to set (of primitive type) doesn't exist - hence, the requirement to check if it exists.
Ted
If you control the classes in question and so know that the class *will* respond to an appropriate selector in the library version that includes your property, use `respondsToSelector:`. In this case, you should use the getter method directly instead of `valueForKey:` since it will be faster.
Barry Wark
Unfortunately, I don't control the classes in question. To make things easier, I've switched to using `NSInvocation` to set the primitively-typed property instead of using Key-Value Coding (and am indeed using `respondsToSelector:`).
Ted