2009-07-08 76 views
36

isKindOfClass: method documentation in NSObject:使用isKindOfClass安全吗:针对NSString实例来确定类型?

使用上的一类集群表示的对象此方法时要小心。由于类集群的性质,您返回的对象可能并不总是您所期望的类型。

文档然后继续给的,为什么你不应该问像一个NSArray实例的以下的例子:

// DO NOT DO THIS! 
if ([myArray isKindOfClass:[NSMutableArray class]]) 
{ 
    // Modify the object 
} 

我们给出了不同的使用的一个例子,假设我有一个我想确定是否有NSString或NSArray的NSObject实例。

这两种类型都是类集群 - 但从上面的文档看来,危险在于对isKindOfClass的回答:太肯定(有时候当你真的没有可变数组时,回答YES),而提出问题关于群集中的简单成员资格仍然有效。

一个例子:

NSObject *originalValue; 

// originalValue gets set to some instance 

if ([originalValue isKindOfClass:[NSString class]]) 
    // Do something with string 

这是假设是正确的?使用isKindOfClass真的很安全:针对类集群实例来确定成员资格?我特别感兴趣的是无处不在的NSString,NSArray和NSDictionary的答案,但是我很想知道它是否可泛化。

+2

您在3年的时间里对此有所了解吗?什么样的愚蠢的`NSMutableArray`子类不会是可变的? – 2011-08-21 20:13:49

+1

从实践经验来看,它并不重要;正如你所说我只见过可变的NSMutableArrays,而且我不认为我曾经使用过一个子类(至少有一个是由第三方制作的)。 Objective-C的大部分内容都是关于不明确的约定,我认为您可以确定您可能遇到的任何NSMutableArray子类的行为方式对其他任何Objective-C程序员都有意义。另外,我还要补充一点,我没有遇到过必须做这样的检查的情况 - 任何有数据流的路径应该总是可变的或不可变的。 – 2011-08-21 20:19:25

+0

同意,当你阅读API时会遇到一些附带的问题,但很少在野外。对我来说,这属于“不要写`if'这个'else'条件没有意义的大类'或类似的东西。 – 2011-08-21 22:04:40

回答

30

文档中的警告使用NSMutableArray示例。假设某些开发人员使用NSMutableArray作为他自己实现的新类型数组CoolFunctioningArray的基类。这个CoolFunctioningArray由设计是不可变的。但是,isKindOfClass将返回YESisKindOfClass:[NSMutableArray class],这是真的,但是按设计不是。

isKindOfClass:将返回YES如果接收者某处继承了作为参数传递的类。 isMemberOfClass:仅当接收者是仅作为参数传递的类的实例(即不包括子类)时才会返回YES

通常isKindOfClass:不安全用于测试成员身份。如果实例为isKindOfClass:[NSString class]返回YES,那么只知道它将响应NSString类中定义的所有方法,但您无法确切知道这些方法的实现可能会执行哪些操作。有人可能会将NSString分类为引发长度方法的例外。

我认为你可以使用isKindOfClass:进行这种测试,特别是如果你正在处理自己的代码,你(作为一个好的撒玛利亚人)设计的代码会以我们所有人期望的方式进行响应(例如NSString子类的长度方法返回它表示的字符串长度而不是引发异常)。如果您使用了许多奇怪开发人员的外部库(例如CoolFunctioningArray的应用程序开发人员),则应谨慎使用isKindOfClass方法,并最好使用isMemberOfClass:方法(可能需要多次来测试一组课程)。

+1

用于说明isKindOfClass和isMemberOfClass之间的区别。我碰巧也需要这个答案。 – 2012-10-04 08:05:06

12

您正在阅读警告错误。它所说的仅仅是因为它在内部被表示为可变的东西,并不意味着你应该试图改变它,因为改变它可能会违反你和任何你得到数组的人之间的契约;它可能违反了他们不会改变它的假设。

但是,isKindOfClass:给出的测试肯定仍然有效。如果[something isKindOfClass:[NSMutableArray class]],那么它是NSMutableArray或其子类的实例,这非常意味着它是可变的。

5

这是苹果文档中的一个奇怪的警告。这就像是在说:“小心这个功能,因为如果你将其与其他破碎的代码一起使用,它将无法工作。”但你可以用任何东西来说。

如果设计遵循它应该的替代原则,那么KindOfClass将起作用。 (wikipedia: Liskov Substitution Principle

如果有些代码违反了原则,通过返回一个不响应addObject的NSMutableArray子类的实例:以及其他NSMutableArray方法,那么该代码有一个问题......除非它只是一个小进制临时黑客...

13

让我们考虑下面的代码:

if ([dict isKindOfClass:[NSMutableDictionary class]]) 
{ 
    [(NSMutableDictionary*)dict setObject:@1 forKey:@"1"]; 
} 

我认为苹果的音符问题引述的真正目的是,这种代码很容易导致崩溃。这里的问题在于与CoreFoundation进行免费桥接。让我们更详细地考虑4个变种:

NSDictionary* dict = [NSDictionary new]; 
NSMutableDictionary* dict = [NSMutableDictionary new]; 
CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 

确实,在所有4个变种中,dict的真实类将是__NSCFDictionary。所有4个变体都将通过第一个代码片段中的测试。

*终止应用程序由于未捕获的异常 'NSInternalInconsistencyException',原因是::但2例(NSDictionary中和CFDictionaryRef声明),我们将与日志类似的东西崩溃“ - [__ NSCFDictionary的setObject:forKey:]:变异的方法发送到不可变的对象'

所以,事情变得更清晰一点。在2种情况下,我们可以修改对象,2种情况下不允许修改对象。它依赖于创建函数,可能的可变性状态由__NSCFDictionary对象本身来跟踪。

但是,为什么我们使用相同的类为可变和不可变的对象? 可能的答案 - 因为C语言的限制(并且CoreFoundation是一个C API,正如我们所知)。我们在C中遇到什么问题?可变和不可变字典类型的声明应该反映如下:CFMutableDictionaryRef类型是CFDictionaryRef的子类型。但是C没有这个机制。但是我们希望能够在CFDictionary对象的函数中传递CFMutableDictionary对象,并且最好不用编译器发出恼人的警告。我们必须做什么?

让我们来看看下面的CoreFoundation类型声明:

typedef const struct __CFDictionary * CFDictionaryRef; 
typedef struct __CFDictionary * CFMutableDictionaryRef; 

正如我们所看到的,CFDictionary和CFMutableDictionary由同一类型表示,只有在const修饰不同。所以,这里开始麻烦。

这同样也适用于NSArray,但情况稍微复杂一点。当你直接创建NSArray或NSMutableArray时,你会得到一些特殊的类(分别是__NSArrayI和__NSArrayM)。对于这个类的崩溃没有转载。但是,当你创建CFArray或CFMutableArray时,事物保持不变。所以要小心!