何时使用enumerateObjectsUsingBlock vs.

时间:2010-12-20 03:19:33

标签: objective-c multithreading ios4 objective-c-blocks

除了明显的差异:

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

enumerateObjectsUsingBlock也有效时,for (id obj in myArray)通常会被认为更好还是更差?有哪些优点/缺点(例如性能或多或少)?

6 个答案:

答案 0 :(得分:346)

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

虽然for(... in ...)非常方便且语法简洁,但enumerateObjectsUsingBlock:有许多功能可能会或可能不会很有趣:

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

  • “当你需要修改局部变量时,不要使用enumerateObjectsUsingBlock” - 不是真的;你可以将你的本地人声明为__block,并且他们在街区内可写。

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

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

就我个人而言,我比enumerateObjectsUsingBlock:更频繁地使用for (... in ...),但是 - 再次 - 个人选择。

答案 1 :(得分:81)

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

一个简单的循环也读得更清楚。这是两个版本的样板:

for (id x in y){
}

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

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

那么你何时应该使用enumerateObjectsUsingBlock:?当您存储块以便稍后执行或在多个位置执行时。当你实际上使用一个块作为一流函数而不是一个循环体的过度替代时,这是很好的。

答案 2 :(得分:41)

虽然这个问题很老,但事情没有改变,接受的答案是错误的。

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

  • 它允许应用任意的非本地逻辑。即,您不需要知道该块在阵列上使用它的作用。
  • 大型集合的并发枚举或繁重计算(使用withOptions:参数)

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

快速枚举受益于代码简洁,可读性和additional optimizations,这使得它非常快速。比旧的C for循环更快!

快速测试得出结论,在2014年的iOS 7上,enumerateObjectsUsingBlock始终比for-in慢700%(基于100项阵列的1mm迭代)。

这里的表现是一个真正的实际问题吗?

绝对不是,除了罕见的例外。

关键是要证明在没有充分理由的情况下使用enumerateObjectsUsingBlock:优于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

答案 3 :(得分:23)

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

选项包括:

1)makeObjectsPerformSelector

[arr makeObjectsPerformSelector:@selector(_stubMethod)];

2)快速枚举&amp;定期发送消息

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

3)enumerateObjectsUsingBlock&amp;定期发送消息

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

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

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

答案 4 :(得分:3)

当您想要破坏嵌套循环时,使用enumerateObjectsUsingBlock作为外部循环非常有用。

e.g。

[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语句。

答案 5 :(得分:1)

感谢@bbum和@Chuck开始全面比较性能。很高兴知道这是微不足道的。我好像已经离开了:

  • <android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent"> - 作为我的默认转到。对我来说更直观,这里的编程历史比任何真正的偏好都要多 - 跨语言重用,由于IDE自动完成,大多数数据结构的输入更少:P。

  • for (... in ...) - 需要访问对象和索引时。当访问非数组或字典结构(个人偏好)时

  • enumerateObject... - 对于数组,当我需要从非零索引开始时