删除数组枚举中的对象

时间:2017-03-27 07:13:04

标签: ios objective-c

[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    if (condition) {
        [array removeObject:obj];
    }
}];

有时它有效,有时会崩溃,为什么?

3 个答案:

答案 0 :(得分:1)

想象一下,如果您在Apple工作并要求添加此代码,您将如何为-enumerateObjectsUsingBlock:编写代码。它可能看起来像:

-(void) enumerateObjectsUsingBlock: (void(^)(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop))myBlock
{
    NSUInteger numItems = [self count];
    BOOL       stop = false;
    for( NSUInteger x = 0; x < numItems && stop == false; x++ )
    {
        myBlock( [self objectAtIndex: x], x, &stop );
    }
}

现在,如果你的块调用removeObject:,那就意味着数组:

0: A
1: B
2: C
3: D

myBlock( A, 0 )更改为:

0: B
1: C
2: D

现在x++被执行了,所以下一个电话是myBlock( C, 1 ) - 你已经看到现在跳过了'B',最初在索引 2 的项目是删除秒(而不是索引1处的那个)。删除后,我们再次循环,数组如下所示:

0: B
1: D

因此当-enumerateObjectsUsingBlock:尝试删除索引2处的项目时,它会从数组的末尾运行,然后崩溃。

简而言之,-enumerateObjectsUsingBlock:的文档并未说明您在迭代时可能修改数组的任何地方,并且块无法告诉{{1}中的循环代码它只是删除了一个对象,所以你不能依赖它。

(您可以自己尝试一下......将此版本的enumerateObjectsUsingBlock重命名为myEnumerateObjectsUsingBlock:,在NSArray上的类别中声明它,并在调试器中使用类似的程序逐步执行它:-enumerateObjectsUsingBlock:)< / p>

如果您希望从阵列中删除项目,则有几种解决方法。一,您可以制作数组的副本,循环遍历该数组,并从原始数组中删除对象。另一种选择是在数组上向后迭代,这意味着早期项的索引不会改变(尝试修改上面[myArray myEnumerateObjectsUsingBlock: ^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop){ [myArray removeObject: obj]; }];的版本并观察调试器中发生的事情)。

另一种方法是编写自己的方法来过滤数组。如果要保留对象,则给它一个预期返回YES的块,否则返回NO。然后循环遍历原始数组,在每个项目上调用块,并创建一个新数组,向其中添加块返回YES的所有对象。

答案 1 :(得分:0)

这是不安全的方式。我不知道这种语言的内部执行情况,顺便说一下,我认为当你删除对象时,数组的大小会减小,当你到达数组的最后一个元素时会出错。 我认为在枚举块中,不允许更改数组。在其他语言上也是同样的问题。 您可以在此网址中获取更多信息。 http://ronnqvi.st/modifying-while-enumerating-done-right/

答案 2 :(得分:-1)

删除后,您的对象不正确。所以复制为:

[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    if (condition) {
  int counter =  [array indexOfObject:obj];
    [array removeObjectAtIndex:counter];
    }
}];