测试只读属性与设置/获取密钥——obj-c / cocoa

Posted

技术标签:

【中文标题】测试只读属性与设置/获取密钥——obj-c / cocoa【英文标题】:test for read-only property vs. set/get key -- obj-c / cocoa 【发布时间】:2013-08-27 00:10:49 【问题描述】:

如果我只有一个键列表,是否有一种优雅的方法来测试对象的只读与读/写属性?我意识到我可以按弦键:

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

或者更好的是尝试为键设置一个值并检查NSUndefinedKeyException - 但是对非异常行为使用异常似乎是一种糟糕的形式。

为了清楚起见,我想以编程方式审核对象的属性并区分例如,

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

编辑:清楚地说,键是实现为 @propertys 还是手动 getter/setter 并不重要。只关心公共接口。感谢您询问我想要完成的工作 - 首先要做到这一点可能更重要。

我正在尝试勾勒出一些生成对象键的图形表示的代码。所有的键都是事先知道的——但我并不总是知道哪些键是可设置的(这取决于特定的子类来实现)

【问题讨论】:

在类扩展中可以将readonly 属性声明为readwrite,这对您的实现是否重要? 您还应该考虑到您可能有一个声明为readonly 的属性,然后去手动实现一个setter - objc 运行时恶作剧可能看起来很有趣,但很可能会给出很多假阴性,说事情是readonly,当它们实际上是通过重新声明或手动实现 setter 进行读写时。 您真正想要实现的目标是什么? @paul.s 对。如果您关心 ivar 是否有关联的 setter,请相信 respondsToSelector:。如果您专注于声明属性的方式,那么询问运行时是一种处理它的优雅方式。 @Paul.s - 感谢您的警告和好问题。我已经编辑了我的问题,试图更具体地说明问题。 【参考方案1】:

您有两种有效的方法:

respondsToSelector: 方法 Tommy 的回答中公开的运行时“技巧”

我将尝试总结这两种方法的含义以及它们的缺点。然后,您可以选择更适合您需求的方法。

案例一

//MyClass.h

@property (readonly) NSString *someReadOnlyKey;
运行时 => 只读 respondsToSelector =>

好的,一切都按预期进行。

案例 2

//MyClass.h

@property (readonly) NSString *someReadOnlyKey;

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

//MyClass.m

@property (readwrite) NSString *someReadOnlyKey;
运行时 => 读写 respondsToSelector =>

如果属性定义已被覆盖,您将获得正在使用的 actual 属性属性。无论您是从 setter 可见的位置(即在类定义内部还是从外部)查询它无关紧要。您将获得用于合成访问器方法的实际定义。

案例 3

//MyClass.h

@property (setter=myCoolSetter) NSString *someReadOnlyKey;
运行时 => 读写 respondsToSelector =>

使用自定义 setter 名称,respondsToSelector 技巧将不再起作用,而运行时仍在提供正确的信息。

案例 4

//MyClass.h

@property (readonly) NSString *someReadOnlyKey;

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

//MyClass.m

- (void)setSomeReadOnlyKey:(NSString *)someReadOnlyKey 
    _someReadOnlyKey = someReadOnlyKey;

运行时 => 只读 respondsToSelector =>

这次运行时失败了,因为 setter 在那里,但属性定义“不知道”它。

【讨论】:

【参考方案2】:

假设您可以询问元类(即,您不想为调度表提供潜在的特定于实例的补丁),您可以从the runtime 获得property attributes。具体来说:

// 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);

然后您可以检查这些属性是否为只读:

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

return [attributes containsObject:@"R"];

如果您想彻底彻底,您还应该通过class_copyProtocolListprotocol_getProperty 检查协议,以便捕获以这种方式合并到类中的任何@propertys,并且——正如Gabriele 下面所指出的——一些注意事项适用于类扩展。

【讨论】:

为什么这比使用 respondsToSelector 更好? 从技术上讲,您可以为 ivar 提供一个 setter 实现,并且仍然将属性声明为 readonly。这在很大程度上取决于 OP 的需求。 除了区分语义上不同的“实现了什么设置器”和“什么@propertys 被声明为读/写”,这将捕获任何一个设置器被赋予不寻常名称的实例。 @Tommy 同意,但它可能会错过属性在外部声明为 readonly 并在类扩展中重新定义为 readwrite 的情况。答案是正确的,但问题并不完全清楚。 调度表的实例特定补丁?这是现在可能的事情吗?最后我检查了一下,更改对象方​​法列表的唯一方法是更改​​其类。这有改变吗?【参考方案3】:

这可能是我考虑异常路径的少数几次之一 - 当然,如果您需要处理 KVC。

原因在于仅检查 set<key>: 样式的方法名称并不足以确定某些内容是否真正可设置,尤其是当您将 KVC 引入其中时。

当您尝试设置值时,KVC 具有明确定义的搜索模式,这可以在 Key Value Coding Programming Guide 中看到。这个搜索路径有很多停止点,甚至允许对象最后一次机会“你确定你不想处理这个 setter”方法与setValue:forUndefinedKey:,可以用来防止NSUndefinedKeyException 异常被抛出

【讨论】:

【参考方案4】:

您可以使用以下内容:

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

else
   // Readonly property

这应该可行。本质上,我正在检查 setter 方法是否存在。

【讨论】:

以上是关于测试只读属性与设置/获取密钥——obj-c / cocoa的主要内容,如果未能解决你的问题,请参考以下文章

Obj-C - 添加“跳过备份”属性总是失败

是否可以在 iOS 中使用 KVC 将值设置为只读属性?

javascript 设置input框只读属性 获取disabled后的值并传给后台

Jquery有无简单的一句话获取所有非只读的文本框集合方法

Java如何设置文件为只读

Obj-C:读取照片库中视频的 URL