为什么在循环标准失败时快速枚举工作?

时间:2012-02-14 20:34:52

标签: objective-c ios cocoa-touch

我有一个充满按钮的UIView * buttonView。我需要更新我的列表并重新填充我的buttonView。我实施了:

if ([[self.buttonView.subviews objectAtIndex:i] isKindOfClass:[UIButon class]]
    [[self.buttonView.subviews objectAtIndex:i] removeFromSuperview];

但它失败了,它不会删除所有按钮(我的测试有8个按钮,它每隔一个按钮删除一次: - ?)

然后我尝试了:

for(UIView *subview in self.buttonView.subviews) 
{
    if([subview isKindOfClass:[UIButton class]]) 
        [subview removeFromSuperview];
}

它完美无缺。

这两个循环不应该完成同样的事情吗?

我猜我还有一些我不知道的关于快速枚举的内容可以解释这一点吗?

4 个答案:

答案 0 :(得分:5)

你的根本问题是你在迭代时改变了一个集合。如果您确实需要这样做,您可能需要考虑向后迭代(使用-reverseObjectEnumerator或使用-enumerateObjectsWithOptions:usingBlock:反向选项)。或者,您可以在迭代之前创建数组的副本。

快速枚举的原因可能是由于调用-subviews返回子视图数组的某种常量副本。这实际上可以在你的第一种情况下使用,只需调用-subviews一次并重新使用结果,但这会引入对实现细节的依赖,我强烈建议避免它。如果你需要一个数组的常量副本,你应该自己制作一个。


解决案例1的最快解决方案是:

NSArray *subviews = [NSArray arrayWithArray:self.buttonView.subviews];
// now enumerate using the subviews array directly instead of calling self.buttonView.subviews

对于快速枚举的情况,您可能希望将其重写为

for (UIView *subview in [NSArray arrayWithArray:self.buttonView.subviews]) {
    // ...
}

第三种选择是切换到基于块的枚举并反向进行。如果你能做到这一点,这实际上将是最有效的选择:

[self.buttonView.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(UIView *subview, NSUInteger idx, BOOL *stop) {
    if ([subview isKindOfClass:[UIButton class]]) {
        [subview removeFromSuperview];
    }
}];

答案 1 :(得分:3)

如果你反向运行它,你的for循环就会起作用(从最高索引开始并在i > 0循环时)。编写for循环的方式,每次删除对象时,索引都会发生变化。编写枚举的方式,可以安全地删除枚举中的对象,或者预先返回所有对象,这样就不会有任何跳过任何元素的机会。

答案 2 :(得分:2)

我不相信你可以修改你所枚举的收藏品,所以要小心。看起来好像处理变化就好了,它会遍历每个项目(与for循环不同)。

在第一种情况下,它不起作用,因为一旦你删除索引0,其他东西变成索引0,但你已经转移到索引1.你可以通过减少循环变量来修复它,如果删除成功,以便循环将其递增到其先前的值:

if ([[self.buttonView.subviews objectAtIndex:i] isKindOfClass:[UIButton class]]) {
    [[self.buttonView.subviews objectAtIndex:i] removeFromSuperview];
    --i;
}

for循环会发生什么:

1,2,3,4,5,6,7,8并删除第一项(1)

2,3,4,5,6,7,8并删除第二项(3)

2,4,5,6,7,8并删除第三项(5)

2,4,6,7,8并删除第四项(7)。

2,4,6,8你在索引5上,这比长度大,所以你已经完成了。

答案 3 :(得分:0)

就像其他人说的那样,你的主要问题是在迭代它时修改一个可变数组。相反,您可以找到所有按钮作为buttonView的子视图,并使它们执行removeFromSuperview。

id buttonClassTest = ^(UIView * subview, NSUInteger idx, BOOL *stop){
    return [subview isKindOfClass:[UIButton class]];
};

NSIndexSet * indexes = [self.buttonView.subviews indexesOfObjectsPassingTest:buttonClassTest];
NSArray * buttons = [self.buttonView.subviews objectsAtIndexes:indexes];
[buttons makeObjectsPerformSelector:@selector(removeFromSuperview)];