Search code examples
objective-ccocoakey-value-coding

test for read-only property vs. set/get key -- obj-c / cocoa


If all i have is a list of keys, is there an elegant way to test an object for read-only vs. read/write properties? I realize I could string-bang the key:

NSString *setterString = [@"set" stringByAppendingString:[someKeyString capitalizedString]];
BOOL isReadWrite = [myObject respondsToSelector:NSSelectorFromString(setterString)];

Or better yet try to set a value for the key and check for an NSUndefinedKeyException - but using exceptions for non-exceptional behavior seems like poor form.

To be clear, i'd like to programmatically audit an object's properties and tell the difference between, for instance,

@property (readonly) NSString *someReadOnlyKey
@property NSString *someReadWriteProperty

edit: To be more clear, it shouldn't matter if the keys are implemented as @propertys or manual getter/setter. Only concerned about public interface. And thanks for asking what I'm trying to accomplish - that's probably more important to get right in the first place.

I'm trying to sketch out some code that generates graphic representations of an object's keys. All the keys are known beforehand - but I won't always know which keys will be sett-able (it's up to the particular subclass to implement)


Solution

  • Assuming you're fine with asking the metaclass (ie, you don't want to allow for potential instance-specific patches to the dispatch table), you can get property attributes from the runtime. Specifically:

    // get the property; yes: that's a C string. This can see only things
    // declared as @property
    objc_property_t property = 
        class_getProperty([instance class], "propertyName");
    
    /* check property for NULL here */
    
    // get the property attributes.
    const char *propertyAttributes = property_getAttributes(property);
    

    You can then check those attributes for read-only:

    NSArray *attributes = 
        [[NSString stringWithUTF8String:propertyAttributes] 
            componentsSeparatedByString:@","];
    
    return [attributes containsObject:@"R"];
    

    If you want to be completely thorough, you should also check protocols via class_copyProtocolList and protocol_getProperty, in order to catch any @propertys that are incorporated into the class that way and — as noted below by Gabriele — some caveats apply around class extensions.