何时使用enumerateObjectsUsingBlock与


150

除了明显的区别:

  • 使用enumerateObjectsUsingBlock时,你既需要索引和对象
  • enumerateObjectsUsingBlock当您需要修改局部变量时不要使用(我错了,请参见bbum的答案)

enumerateObjectsUsingBlock普遍认为是好还是坏的时候for (id obj in myArray)也会工作?优点/缺点是什么(例如,或多或少的性能)?


1
如果需要当前索引,我喜欢使用它。
Besi 2012年

Answers:


349

最终,使用您想要使用的任何模式,并且在上下文中更加自然。

虽然for(... in ...)很方便且语法简短,enumerateObjectsUsingBlock:但其中的一些功能可能会或可能不会很有趣:

  • enumerateObjectsUsingBlock:将比快速枚举快或快(for(... in ...)使用NSFastEnumeration支持来实现枚举)。快速枚举需要从内部表示形式转换为快速枚举的表示形式。其中有开销。基于块的枚举允许集合类枚举内容的速度最快地遍历本机存储格式。与数组可能无关紧要,但对字典而言可能是巨大的不同。

  • “当您需要修改局部变量时,请不要使用enumerateObjectsUsingBlock”-不正确;您可以将您的本地人声明为__block,他们将在代码块中可写。

  • enumerateObjectsWithOptions:usingBlock: 支持并发或反向枚举。

  • 对于字典,基于块的枚举是同时检索键和值的唯一方法。

就个人而言,我使用的enumerateObjectsUsingBlock:次数多于for (... in ...),但-还是个人选择。


16
哇,内容非常丰富。我希望我能接受这两个答案,但是我选择Chuck,因为它在我身上引起了更多共鸣。另外,在搜索__block时,我发现了您的博客(friday.com/bbum/2009/08/29/blocks-tips-tricks),并且学到了更多。谢谢。
保罗惠勒2010年

8
根据记录,基于块的枚举并不总是“那么快”或“更快” mikeabdullah.net/slow-block-based-dictionary-enumeration.html
Mike Abdullah

2
@VanDuTran块仅在您告诉它们在单独的线程上执行时才在单独的线程上执行。除非您使用枚举的并发选项,否则它将
在进行

2
尼克·洛克伍德(Nick Lockwood)在这方面做了一篇非常不错的文章,看来enumerateObjectsUsingBlock它仍然比数组和集合的快速枚举慢得多。我想知道为什么?iosdevelopertips.com/objective-c/...
鲍勃Spryn

2
尽管这是一种实现细节,但应在此答案中提及这两种方法之间的差异,即enumerateObjectsUsingBlock使用自动释放池(至少从OS X 10.10起)包装每次调用该块的两种方法之间的差异。这解释了性能差异与for in不这样做的差异。
Pol 2014年

83

对于简单的枚举,更简单的选择是使用快速枚举(即for…in…循环)。块方法可能稍快一些,但是在大多数情况下并没有太大关系-很少有程序受CPU限制,即使那样,循环本身而不是内部计算很少会成为瓶颈。

一个简单的循环也可以更清晰地读取。这是两个版本的样板:

for (id x in y){
}

[y enumerateObjectsUsingBlock:^(id x, NSUInteger index, BOOL *stop){
}];

即使添加变量来跟踪索引,简单的循环也更易于阅读。

那么什么时候应该使用enumerateObjectsUsingBlock:?当您存储一个块以便以后或在多个地方执行时。当您实际上将块用作一流函数而不是过度使用替代循环体时,这非常有用。


5
enumerateObjectsUsingBlock:在所有情况下,其速度都将与快速枚举相同或更快。 for(... in ...)使用快速枚举,该枚举要求集合提供内部数据结构的一些临时表示。如您所述,可能无关紧要。
bbum 2010年

4
+1When you're storing a block to execute later or in multiple places. It's good for when you're actually using a block as a first-class function rather than an overwrought replacement for a loop body.
史蒂夫

7
@bbum我自己的测试表明,enumerateObjects...使用循环实际上可能比慢速枚举慢。我进行了数千次测试。块和循环的主体是同一行代码:[(NSOperation *)obj cancel];。平均:快速枚举循环- -[JHStatusBar dequeueStatusMessage:] [Line: 147] Fast enumeration time (for..in..loop): 0.000009以及块- -[JHStatusBar dequeueStatusMessage:] [Line: 147] Enumeration time using block: 0.000043。奇怪的是,时差是如此之大且一致,但是显然,这是一个非常具体的测试用例。
chown 2012年

42

尽管这个问题很旧,但情况没有改变,但接受的答案是错误的。

enumerateObjectsUsingBlockAPI并非要取代for-in,而是针对完全不同的用例:

  • 它允许应用任意的非本地逻辑。也就是说,您不需要知道块在数组上使用它的功能。
  • 大型枚举或繁重计算的并发枚举(使用withOptions:参数)

快速枚举for-in仍然是枚举集合的惯用方法。

快速枚举得益于代码简洁,可读性和其他优化功能,这些特性使其异常快速。比旧的C for循环快!

一项快速测试得出的结论是,在2014年的iOS 7上,enumerateObjectsUsingBlock其性能始终比入门级慢700%(基于100项数组的1mm迭代)。

在这里,性能是真正的实际问题吗?

绝对没有,除了极少数例外。

重点是要证明,没有充分的理由使用enumerateObjectsUsingBlock:over 几乎没有好处for-in。它不会使代码更具可读性……或更快……或线程安全。(另一个常见的误解)。

选择取决于个人喜好。对我而言,惯用且易读的选项胜出。在这种情况下,即使用进行快速枚举for-in

基准测试:

NSMutableArray *arr = [NSMutableArray array];
for (int i = 0; i < 100; i++) {
    arr[i] = [NSString stringWithFormat:@"%d", i];
}
int i;
__block NSUInteger length;

i = 1000 * 1000;
uint64_t a1 = mach_absolute_time();
while (--i > 0) {
    for (NSString *s in arr) {
        length = s.length;
    }
}
NSLog(@"For-in %llu", mach_absolute_time()-a1);

i = 1000 * 1000;
uint64_t b1 = mach_absolute_time();
while (--i > 0) {
    [arr enumerateObjectsUsingBlock:^(NSString *s, NSUInteger idx, BOOL *stop) {
        length = s.length;
    }];
}
NSLog(@"Enum %llu", mach_absolute_time()-b1);

结果:

2014-06-11 14:37:47.717 Test[57483:60b] For-in 1087754062
2014-06-11 14:37:55.492 Test[57483:60b] Enum   7775447746

4
我可以确认,在MacBook Pro Retina 2014上运行相同的测试enumerateObjectsUsingBlock实际上要慢5倍。显然,这是由于自动释放池包装了该块的每次调用,在这种for in情况下不会发生。
2014年

2
我确认enumerateObjectsUsingBlock:使用Xcode 7.x构建的真实iPhone 6 iOS9仍然慢4倍。
2015年

1
感谢您的确认!我希望这个答案不会被埋没...有些人喜欢enumerate语法,因为它感觉像FP,并且不想听性能分析。
亚当·卡普兰

1
顺便说一句,这不仅仅是由于自动释放池。还有更多的堆栈推入式操作enumerate:
Adam Kaplan,

24

为了回答有关性能的问题,我使用性能测试项目进行了一些测试。我想知道将消息发送到数组中所有对象的三个选项中哪一个最快。

选项包括:

1)makeObjectsPerformSelector

[arr makeObjectsPerformSelector:@selector(_stubMethod)];

2)快速枚举和常规消息发送

for (id item in arr)
{
    [item _stubMethod];
}

3)enumerateObjectsUsingBlock和常规消息发送

[arr enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) 
 {
     [obj _stubMethod];
 }];

事实证明,makeObjectsPerformSelector是最慢的。快速枚举花费了两倍的时间。enumerateObjectsUsingBlock最快,比快速迭代快15-20%。

因此,如果您非常担心最佳性能,请使用enumerateObjectsUsingBlock。但是请记住,在某些情况下,枚举集合所花费的时间与运行每个对象要执行的任何代码所花费的时间相形见war。


您能指出您的特定测试吗?您似乎给出了错误的答案。
2015年

3

当您想中断嵌套循环时,将enumerateObjectsUsingBlock用作外部循环是非常有用的。

例如

[array1 enumerateObjectsUsingBlock:^(id obj1, NSUInteger idx, BOOL * _Nonnull stop) {
  for(id obj2 in array2) {
    for(id obj3 in array3) {
      if(condition) {
        // break ALL the loops!
        *stop = YES;
        return;
      }
    }
  }
}];

另一种方法是使用goto语句。


1
或者您也可以像在这里一样从方法中返回:-D
Adam Kaplan,

1

感谢@bbum和@Chuck开始对性能进行全面比较。很高兴知道这很简单。我似乎已经同意:

  • for (... in ...)-作为我的默认goto。对我而言,这里的编程历史比任何真正的偏好都更直观-跨语言重用,由于IDE自动完成:P,大多数数据结构的键入次数更少。

  • enumerateObject...-需要访问对象和索引时。并且在访问非数组或字典结构时(个人喜好)

  • for (int i=idx; i<count; i++) -对于数组,当我需要从非零索引开始时

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.