简体   繁体   中英

How do you tell if a key exists for an object using Key-Value Coding?

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?

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, eg, for a property foo you would call something like [someObject respondsToSelector:NSSelectorFromString(@"foo")]; .

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).

Following on from @Jason Coco answer.

Here is my offering on how to check specifically for the "set" selectors existence of the property you're trying to set.

This is pure filth but it works and I've found myself using it. You've been warned..

NS_INLINE SEL _Nonnull IBPropertySetSelectorForPropertyName(NSString*_Nonnull propertyName)
{

    NSString* firstLetter = [propertyName substringToIndex:1];
    propertyName = [propertyName substringFromIndex:1];

    propertyName = [[NSString alloc] initWithFormat:@"set%@%@:",firstLetter.capitalizedString,propertyName];

    return NSSelectorFromString(propertyName);
}

Usage:

In swift: Add the code snippet to your bridging header then:

let key = "someKey"    
let sel = IBPropertySetSelectorForPropertyName(key)

if SOMETHING.responds(to: sel) {SOMETHING.setValue(value, forKeyPath: key)}

The try/catch approach you propose in your question is the only reliable way of knowing whether valueForKey: will throw an exception.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM