Objective-C的enumerateObjectsUsingBlock和快速枚举有何区别?

35

以下两种方法的优缺点是什么:

使用block枚举

NSArray *myArray = [NSArray new];
[myArray enumerateObjectsUsingBlock:^(id anObject, NSUInteger idx, BOOL *stop) {
    if (anObject == someOtherObject) {
        [anObject doSomething:idx];
        *stop = YES;
    }
}];

快速枚举

NSArray *myArray = [NSArray new];
int idx = 0
for (id anObject in myArray) {
    if (anObject == someOtherObject) {
        [anObject doSomething:idx];
        break;
    }
    ++idx;
}

您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Cœur
2个回答

49

这篇博客文章涵盖了主要区别。总结如下:

  • 快速枚举在OS X 10.5+上可用,块在10.6+上可用
  • 对于简单的枚举,快速枚举比基于块的枚举略快一点
  • 使用基于块的枚举比使用快速枚举更容易(及性能更好)进行并发或反向枚举
  • 当枚举 NSDictionary 时,使用基于块的枚举器可以一次获取键和值,而使用快速枚举需要使用键来单独检索值。

关于最后一点(NSDictionary枚举),请参考以下内容:

for (id key in dictionary)
{
    id obj = [dictionary objectForKey: key];
    // do something with key and obj
}

变成这样:

[dictionary enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop) {
    // do something with key and obj
}];

此外,这两种方法都可以保护可变集合免于在枚举循环内部发生突变。有趣的是,如果您试图在基于块的枚举中改变集合,您将会被 CoreFoundation 的 __NSFastEnumerationMutationHandler 抛出异常,这表明底层存在一些公共代码。

 NSMutableArray *myArray = [NSMutableArray arrayWithObjects:@"a", @"b", nil];
 [myArray enumerateObjectsUsingBlock:^(id anObject, NSUInteger idx, BOOL *stop) {
     // Attempt to mutate the array during enumeration
     [myArray addObject:@"c"];
 }];

输出:

2011-12-14 22:37:53.716 Untitled[5809:707] *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0x109614190> was mutated while being enumerated.'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff8cca7286 __exceptionPreprocess + 198
    1   libobjc.A.dylib                     0x00007fff8319ad5e objc_exception_throw + 43
    2   CoreFoundation                      0x00007fff8cd311dc __NSFastEnumerationMutationHandler + 172
    3   CoreFoundation                      0x00007fff8cc9efb4 __NSArrayEnumerate + 612
    4   Untitled                            0x00000001094efcea main + 250
    5   Untitled                            0x00000001094efbe4 start + 52
    6   ???                                 0x0000000000000001 0x0 + 1
)
terminate called throwing an exceptionRun Command: line 1:  5809 Abort trap: 6           ./"$2"

撇开性能不谈,你们在第一眼看上去觉得哪个版本更易读? - Ricardo Sanchez-Saez
1
有趣的问题!我发现在字典键上使用for循环更易读。代码块语法对我来说仍然不直观。 - Simon Whitaker
对于键值,我发现块更直观(我来自C(++),只使用过一次块)。 - Helin Wang
可能是因为我偶尔使用Python。 - Helin Wang
1
我并不是说这是错的,但是这篇帖子表明基于块(block based)更高效。https://dev59.com/NW855IYBdhLWcg3wMBPV#4487012 - Robert

4

我想到的第一个想法

  • 块在iOS 4及更高版本中可用,因此如果您需要支持旧版本,则不能使用块语法。

  • 它们在功能上相当,除了在块版本中无法意外破坏计数器。

  • 另一个潜在的区别是,您可以在其他地方定义块,并根据您的状态传递不同的块。

希望这只是一个非常粗略的示例,因为代码片段相当糟糕,有更有效的方法来完成这个任务 ;)


此外,如果我没记错的话,在 WWDC 2010 视频中提到,块枚举甚至比快速枚举更高效。 - Mark Adams
1
这就是视频的问题,你知道里面有好东西,但是要在以后再找到它就需要好运气了。我查看了枚举:遍历集合元素的文档,但没有明显提到性能提升的内容 :S - Paul.s
2
其实,我刚刚找到了它。这是2010年发布的“iOS 4基础知识更新”视频。 - Mark Adams
干得好!即使知道视频在哪里,要追踪它仍然很困难。 - Paul.s

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