NSArray不同类型的快速枚举

5

我有一个问题,它在此处(以及SO上的其他问题),还有关于Objective-C集合和快速枚举的苹果文档。不清楚的是,如果使用不同类型填充了NSArray,并创建了循环,例如:

for ( NSString *string in myArray )
    NSLog( @"%@\n", string );

这里到底发生了什么?循环会跳过任何不是NSString的东西吗?举个例子,假设一个UIView在数组中,当循环遇到该项时会发生什么?


3
快速枚举"探听你的话"对类进行操作。 @awfullyjohn 提供了处理具有未知类成员的数组的最佳解决方案。没有隐式过滤,可能会导致调用接收器无法处理的方法... 崩溃。 - Jesse Black
5个回答

9
为什么你想这样做?我认为这会导致错误和意外行为。如果你的数组中填充了不同的元素,请使用以下方法代替:
for (id object in myArray) {
    // Check what kind of class it is
    if ([object isKindOfClass:[UIView class]]) {
       // Do something
    }
    else {
       // Handle accordingly
    }
}

您在示例中所做的实际上与以下内容相同,
for (id object in myArray) {
    NSString *string = (NSString *)object;
    NSLog(@"%@\n", string);
}

仅仅因为你将object转换成(NSString *)并不意味着string实际上指向一个NSString对象。以这种方式调用NSLog()将根据NSObject协议调用- (NSString *)description方法,该协议所引用的类可能符合规范也可能不符合规范。如果符合规范,它将打印出来。否则,它将崩溃。


这个问题更多地涉及到对Objective-C的深入理解,而不是像这样实际做些什么。此外,我最近一直在使用C#,其中可以指定进入集合的类型。 - Mike D
1
没错。我会更新我的回答,但基本上你比我先完成了。 - john
1
你的帖子需要做一个小澄清:NSObject 本身符合 NSObject 协议,因此任何继承自 NSObject 类的对象(即除了极少数之外的所有对象)也符合该协议。 - jscs
1
@JoshCaswell 看得出你很细心。我只是想更深入地解释一下在幕后发生了什么,并且给出一个可能存在错误的行为的例子。也许这个特定的情况并不是最常见的问题,但是要记住实际调用的内容是什么,以避免潜在的崩溃是很重要的,因为即使像 NSLog() 这样看似无害的调用也不会在未定义类型或错误类型的对象上始终正常工作。 - john

3
您需要了解的是,obj-c中的指针没有类型信息。即使您写了NSString*,这只是编译检查。在运行时,一切都只是一个id
Obj-c运行时从不检查对象是否属于给定类。您可以将NSNumbers放入NSString指针中而不会出现问题。仅当您尝试调用未在对象上定义的方法(发送消息)时才会出现错误。
快速枚举如何工作?它与以下完全相同:

for (NSUInteger i = 0; i < myArray.count; i++) {
    NSString* string = [myArray objectAtIndex:i];

    [...]
}

它之所以更快,是因为它在更低的层次上运行。


2

我刚刚尝试了一个快速的例子...这是我的代码。

NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:1];
NSNumber *number = [NSNumber numberWithInteger:6];
[array addObject:number];
[array addObject:@"Second"];

现在如果我只是简单地记录对象,没有问题。NSNumber实例被强制转换为NSString,但两种方法都响应-description,所以这不是问题。
for (NSString *string in array)
{
    NSLog(@"%@", string);
}

然而,如果我尝试在NSString上记录-length...
for (NSString *string in array)
{
    NSLog(@"%i", string.length);
}

...当NSNumber无法响应-length选择器时,它会抛出一个NSInvalidArgumentException异常。简而言之,Objective-C给了你很多自由,但不要用它来绊倒自己。


NSNumber没有被转换为NSString。NSNumber和NSString都是NSObject的子类,NSLog调用description方法,就像你所说的那样。但是这里没有涉及到类型转换。 - user1040049
哦,我明白了。我想我只是将强制转换和代码补全功能混淆了。当我写这个例子时,我没有收到有关在 NSNumber 实例上发送 -length 消息的警告。 - Mark Adams
啊!好的...我现在也明白你的意思了。NSNumber被强制转换为NSString并赋值给string。我以为你是说NSLog正在执行转换(从提到description来看)。我的错! - user1040049

1

有趣的问题。快速枚举最通用的语法是

for ( NSObject *obj in myArray )
    NSLog( @"%@\n", obj );

我相信通过做

for ( NSString *string in myArray )
    NSLog( @"%@\n", string );

相反,您只是将每个对象转换为NSString。也就是说,我认为上面的代码等价于

for ( NSObject *obj in myArray ) {
    NSString *string = obj;
    NSLog( @"%@\n", string );
}

我在苹果的快速枚举文档中��有找到准确的提及,但你可以通过一个例子来检查它并看看会发生什么。


1
“最通用”的版本不是for(id obj in arr)吗?(因为它涵盖了(数量很少的)不从NSObject派生的对象)? - jscs

1

由于所有的NSObject都会响应isKindOfClass方法,因此您仍然可以将类型转换降到最低:

for(NSString *string in myArray) {
    if (![string isKindOfClass:[NSString class]])
        continue;
    // proceed, knowing you have a valid NSString *
    // ...
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接