objective-c - enumerateObjectsUsingBlock : and for( in ) 之间的不同行为

标签 objective-c multithreading nsmutablearray nsarray enumeration

给这个代码

NSMutableArray *array = [NSMutableArray new];
for (int i = 0; i < 10000; i++) {
    [array addObject:@(i)];
}

queue1 = dispatch_queue_create("com.test_enumaration.1", DISPATCH_QUEUE_CONCURRENT);
queue2 = dispatch_queue_create("com.test_enumaration.2", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue1, ^{
    int idx = 0;
    for (NSNumber *obj in array) {
        NSLog(@"[%d] %@", idx, obj);
        idx++;
    }
});

double delayInSeconds = 0.3;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, queue2, ^(void){
    [array removeObjectAtIndex:9000];
    NSLog(@"----");
});

我预计此代码会崩溃,因为在某些时候分派(dispatch)到 queue2 的 block 会与枚举同时执行,这将触发断言,即您不能在枚举时改变数组。事实上,这就是发生的事情。

有趣的部分是当您将 for ( in ) 替换为 enumerateObjectsUsingBlock:

NSMutableArray *array = [NSMutableArray new];
for (int i = 0; i < 10000; i++) {
    [array addObject:@(i)];
}

queue1 = dispatch_queue_create("com.test_enumaration.1", DISPATCH_QUEUE_CONCURRENT);
queue2 = dispatch_queue_create("com.test_enumaration.2", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue1, ^{
    [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"[%d] %@",idx, obj);
    }];
});

double delayInSeconds = 0.3;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, queue2, ^(void){
    [array removeObjectAtIndex:9000];
    NSLog(@"----");
});

在我所有不同的测试中,删除对象的 block 在枚举的中间执行(我看到@“----”的打印),有趣的是枚举的行为正确打印 [8999] 8999 然后是 [9000] 9001

在这种情况下,数组在枚举期间发生了变化,但没有触发任何断言。这是预期的行为吗?如果是,为什么?我是不是漏了什么?

最佳答案

自从引入快速枚举以来,它已成为...快速枚举的首选方法。枚举的大多数实现,例如 for(in)enumerateObjectsUsingBlock:,将在幕后使用快速枚举。

快速枚举将查看数据的存储方式。在 NSMutableArray 的情况下,我猜底层数据存储在几个数据 block 中;一个包含一万个项目的数组可以实现为一百个包含 100 个项目的 block ,每个 block 将其一百个项目存储在连续的内存中。对某些程序集的分析表明(至少在某些 iOS 设备上)该类是作为单个巨大的循环缓冲区实现的。无论哪种方式,枚举列表都可能包含多个 个连续的对象 block 。最终,确切的存储机制是无关紧要的; 访问底层的连续存储使快速枚举比替代方法更好。

一般来说,枚举应该防止列表被改变。您将始终在 for(in) 枚举中看到这一点。显然 enumerateObjectsUsingBlock: 的某些实现并不能有力地保证列表在枚举期间不会发生变化。我在我尝试过的设备上遇到断言失败......但听起来有些设备的这种保护已被破坏。我猜 NSFastEnumerationState 中使用的突变守卫并不完整,可能只监视一个 block 而不是整个数组。

我认为这是 enumerateObjectsUsingBlock: 中的错误。

此外,根据定义,此处可能产生异常的任何代码都是错误代码:您需要提供一种机制来防止您自己的代码在另一个线程迭代时尝试修改数组在上面。

关于objective-c - enumerateObjectsUsingBlock : and for( in ) 之间的不同行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22504856/

相关文章:

iphone - NSObject doesNotRecogniseSelector 导致程序崩溃

multithreading - Delphi 2007 中的 AsyncCall

python - 如何中断在 Python 的不同线程中运行的 socket.accept()?

iphone - 将 XML 解析为 NSMutableArray

ios - Mapkit 添加新注释并删除旧注释

iphone - 在其他设备上测试我的 iPad 应用程序

ios - 从应用程序打开 Facebook 页面

multithreading - 如何使用 Grand Central Dispatch 并行化数独求解器?

objective-c - 不能 "Copy"一个可变数组到另一个

objective-c - NSMutableArray addObject 不起作用