我知道Apple has cautioned反对使用它。但鉴于他们的推理,结果远非相关和预期。
这是我的调试输出 - 结果在代码中没有区别 - 以下只是为了简洁:
(lldb) po [@"Hello" isKindOfClass:[NSMutableString class]]
true => A mutable string?
(lldb) po [[@"Hello" mutableCopy] isKindOfClass:[NSMutableString class]]
0x00000001019f3201 => What's that?
(lldb) po [[@"Hello" mutableCopy] isMemberOfClass:[NSMutableString class]]
0x000000010214e400 => What's that?
(lldb) po [@"Hello" isMemberOfClass:[NSMutableString class]]
false => Once again?
除此之外,我删除了所有字符串文字代码并测试了以下内容:
NSMutableString * m = [[NSMutableString alloc] initWithString:@"Hello"];
bool b = [m isKindOfClass:[NSMutableString class]];
NSLog(@"%d", b); --> 1 Expected.
b = [m isKindOfClass:[NSString class]];
NSLog(@"%d", b); --> 1 Expected.
b = [m isMemberOfClass:[NSString class]];
NSLog(@"%d", b); --> 0 Expected.
b = [m isMemberOfClass:[NSMutableString class]];
NSLog(@"%d", b); --> 0 Not Expected.
有启发吗?
更新
Apple自己的看法:
在类所代表的对象上使用此方法时要小心 簇。由于类簇的性质,你得到的对象 返回可能并不总是您期望的类型。如果你打电话给方法 返回一个类集群,该方法返回的确切类型是 你可以用这个对象做什么的最佳指标。
为什么不简单地说不要将isKindOfClass
和isMemberOfClass
用于群集类?
该解释阻止了从以下角度使用:
你最终可能会修改一些你不应该做的事情。
而不是陈述:
这些方法不适用于类集群。 (在示例中,我已经在上面显示 - 我显然正在传递正确的对象,但仍未获得预期的结果。)
更新2:
以Apple Radar提交。
答案 0 :(得分:5)
这些方法不会像您在评论中声称的那样“误导”。由于NSString
和NSMutableString
是类集群,因此它们可以返回任意具体子类的实例,分别是NSString
或NSMutableString
。
实际上,NSString
集群中的大多数具体子类也是NSMutableString
的子类。他们不使用实际的类来控制可变性,而是使用标志或类似的东西。一切都完全有效并符合设计合同。
所以,这就是[@"Hello" isKindOfClass:[NSMutableString class]]
返回true的原因。你问“一个可变的字符串?”不。该表达式不是可变性的有效测试。如文档所述, 没有 有效的可变性测试。这是你误解的核心。您不得尝试询问对象的类以确定它是否可变。您必须遵守API中指针的静态类型。
修改:Concepts in Objective-C Programming: Object Mutability – Receiving Mutable Objects:
中记录了这一点使用返回类型,而不是自省
确定是否可以更改收到的对象,接收方 消息必须依赖于返回值的正式类型。如果它 例如,接收一个类型为immutable的数组对象,它应该 不要试图改变它。这不是一种可接受的编程习惯 根据类成员资格确定对象是否可变 例如:
if ( [anArray isKindOfClass:[NSMutableArray class]] ) { // add, remove objects from anArray }
出于与实施相关的原因,
isKindOfClass:
返回的内容 这种情况可能不准确。但除此之外的原因,你 不应该假设一个对象是否可变 上课成员资格。你的决定应该完全由什么来决定 出售该对象的方法的签名说明了它的可变性。 如果您不确定对象是可变的还是不可变的,请假设 这是不可改变的。有几个例子可能有助于澄清本指南的原因 重要的是:
您从文件中读取了属性列表。当Foundation框架处理列表时,它注意到了各种子集 属性列表是相同的,因此它创建了一组对象 在所有这些子集中共享。然后你看看创建的 属性列表对象并决定改变一个子集。突然间,和 在没有意识到的情况下,你已经在多个地方更改了树。
您要求
NSView
查看其子视图(使用subviews
方法)并返回一个声明为NSArray
但可能是NSMutableArray
的对象 内部NSView
。然后你将该数组传递给其他人 通过内省,确定它是可变的代码 改变它。通过更改此数组,代码正在改变内部数据-isMemberOfClass:
类的结构。所以不要基于什么做出关于对象可变性的假设 内省告诉你一个对象。将对象视为可变或 不是基于您在API边界上的内容(即基于 关于返回类型)。如果您需要明确地将对象标记为 传递给客户端时,可变或不可变,传递给客户端 信息作为标志和对象。
正如其他人所提到的,po
测试是否是该类的实例,而不是任何子类。对于类集群,它总是返回false,因为公共类是抽象的,永远不会有实例。
其他奇怪的结果可能是因为您对非对象值使用p
(“打印对象”的缩写)。使用{{1}}命令来表示布尔表达式。
答案 1 :(得分:2)
tl; dr不要在代码中使用内省来确定可变性或改变行为(在非常有限的情况之外)。使用静态类型和强定义的数据结构(包括强定义的plist结构)。
Objective-C运行时提供的内省功能,其NSObject
上的内省方法是针对它实现的,既不会产生误导,也不会返回不正确的结果。
他们揭示了各种对象如何实现的具体细节。这可能与头文件中声明的内容完全不同。
或者,换句话说:编译时间与运行时间可能会有很大不同。
这既正确又正常。令人困惑。
Objective-C实现了 duck typing 的强大概念。也就是说,如果您对声明为NSString*
的内容有引用,那么真正引用的内容无关紧要,只要它响应为该类声明的API合同即可NSString
。
混淆来自于试图将Objective-C视为一种完全动态的,内省驱动的语言。它不是为此而设计的(好吧,它有点像,但这个概念大约在1990年被删除)并且,随着时间的推移,强类型变得越来越常见。即让编译器弄清楚某些东西是否有效,并且不要试图在运行时再次猜测。