使用isKindOfClass:对NSString实例确定类型是否安全?

时间:2009-07-08 08:19:56

标签: objective-c

来自isKindOfClass: method documentation in NSObject:

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

然后文档继续给出一个例子,说明为什么你永远不应该问一下NSArray实例的以下内容:

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

现在举一个不同用法的例子,假设我有一个NSObject实例,我想确定我是否有NSString或NSArray。

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

一个例子:

NSObject *originalValue;

// originalValue gets set to some instance

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

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

4 个答案:

答案 0 :(得分:30)

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

如果接收器某处继承自作为参数传递的类,

isKindOfClass:将返回YESisMemberOfClass:仅在接收者是仅作为参数传递的类的实例时返回YES,即不包括子类。

通常isKindOfClass:用于测试会员资格是不安全的。如果实例为YES返回isKindOfClass:[NSString class],您只知道它将响应NSString类中定义的所有方法,但您不确定这些方法的实现可能是什么做。有人可能已经将NSString子类化为长度方法引发异常。

我认为您可以使用isKindOfClass:进行此类测试,尤其是当您使用自己的代码时,您(作为一名优秀的撒玛利亚人)以这样的方式设计它会以一种我们所有的方式响应期望它(例如NSString子类的长度方法返回它所代表的字符串的长度,而不是引发异常)。如果您正在使用许多奇怪开发人员的外部库(例如应该被拍摄的CoolFunctioningArray的开发人员),则应谨慎使用isKindOfClass方法,并最好使用{{ 1}}方法(可能多次测试一组类的成员资格)。

答案 1 :(得分:16)

让我们考虑以下代码:

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

实际上,在ALL 4变体中,dict的真正类将是__NSCFDictionary。所有4种变体都将在第一个代码片段中通过测试。但在2个案例中(NSDictionary和CFDictionaryRef声明),我们将使用类似的日志崩溃:

* 由于未捕获的异常'NSInternalInconsistencyException'而终止应用程序,原因:' - [__ NSCFDictionary setObject:forKey:]:发送到不可变对象的mutating方法'

所以,事情变得更加清晰了。在2种情况下,我们可以修改对象,而在2种情况下则不允许。它取决于创建函数,并且可能由__NSCFDictionary对象本身跟踪可变性状态。

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

让我们看看以下CoreFoundation类型声明:

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

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

同样一般也适用于NSArray,但情况稍微复杂一些。直接创建NSArray或NSMutableArray时,您将获得一些特殊类(分别为__NSArrayI和__NSArrayM)。对于这个类崩溃没有转载。但是当你创建CFArray或CFMutableArray时,事情仍然是一样的。所以要小心!

答案 2 :(得分:12)

您正在阅读警告错误。它只是因为它在内部被表示为可变的东西,并不意味着你应该试图改变它,因为改变它可能会违反你和你从阵列中得到的任何人之间的合同;它可能违反了他们不会改变它的假设。

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

答案 3 :(得分:5)

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

如果设计遵循替换原则,那么isKindOfClass将起作用。 (wikipedia: Liskov Substitution Principle

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