为什么isKindOfClass和isMemberOfClass与NSString和NSMutableString一起使用效果不佳?

时间:2016-01-01 23:28:55

标签: ios class nsstring objective-c-runtime nsmutablestring

我知道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自己的看法:

  

在类所代表的对象上使用此方法时要小心   簇。由于类簇的性质,你得到的对象   返回可能并不总是您期望的类型。如果你打电话给方法   返回一个类集群,该方法返回的确切类型是   你可以用这个对象做什么的最佳指标。

为什么不简单地说不要将isKindOfClassisMemberOfClass用于群集类?

该解释阻止了从以下角度使用:

  

你最终可能会修改一些你不应该做的事情。

而不是陈述:

  

这些方法不适用于类集群。   (在示例中,我已经在上面显示 - 我显然正在传递正确的对象,但仍未获得预期的结果。)

更新2:

Apple Radar提交。

2 个答案:

答案 0 :(得分:5)

这些方法不会像您在评论中声称的那样“误导”。由于NSStringNSMutableString是类集群,因此它们可以返回任意具体子类的实例,分别是NSStringNSMutableString

实际上,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年被删除)并且,随着时间的推移,强类型变得越来越常见。即让编译器弄清楚某些东西是否有效,并且不要试图在运行时再次猜测。