[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (condition) {
[array removeObject:obj];
}
}];
有时它有效,有时会崩溃,为什么?
答案 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];
}
}];