문제

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)

도움이 되었습니까?

해결책 2

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.

다른 팁

You have two valid approaches:

  • the respondsToSelector: method
  • the runtime "trick" as exposed in Tommy's answer

I'll try to summarize the implication of both approaches, along with their drawbacks. You can then choose the approach more suitable to your needs.

Case 1

//MyClass.h

@property (readonly) NSString *someReadOnlyKey;
  • runtime => readonly
  • respondsToSelector => NO

Ok, everything works as expected.

Case 2

//MyClass.h

@property (readonly) NSString *someReadOnlyKey;

///////////////////////////////////////////////////////////////////////////

//MyClass.m

@property (readwrite) NSString *someReadOnlyKey;
  • runtime => readwrite
  • respondsToSelector => YES

If the property definition has been overridden, you'll get the actual property attribute being used. It doesn't matter whether you query about it from where the setter is visible, i.e. inside the class definition, or from outside. You will get the actual definition that has been used to synthesize the accessor methods.

Case 3

//MyClass.h

@property (setter=myCoolSetter) NSString *someReadOnlyKey;
  • runtime => readwrite
  • respondsToSelector => NO

With a custom setter name, the respondsToSelector trick won't work anymore, whereas the runtime is still providing the correct information.

Case 4

//MyClass.h

@property (readonly) NSString *someReadOnlyKey;

///////////////////////////////////////////////////////////////////////////

//MyClass.m

- (void)setSomeReadOnlyKey:(NSString *)someReadOnlyKey {
    _someReadOnlyKey = someReadOnlyKey;
}
  • runtime => readonly
  • respondsToSelector => YES

This time the runtime is failing, since the setter is there, but the property definition "doesn't know" about it.

This might be one of the few times I might consider the exception route - that is of course if you need handle KVC.

The reason being that just checking for a set<key>: style method name is not really enough to determine if something is really settable, especially when you bring KVC into it.

KVC has well defined search patterns for when you try to set values, which can be seen in the Key Value Coding Programming Guide. This search path has many stops and even allows objects a last chance "are you sure you don't want to handle this setter" method with setValue:forUndefinedKey:, which can be used to prevent the NSUndefinedKeyException exception being thrown

You can use the following:

if ([obj respondsToSelector:@selector(setSomeReadOnlyKey:someReadOnlyKey:)]) {
   // Not a readonly property
}
else
   // Readonly property

This should work. Essentially, I am checking if the setter method exists.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top