何时使用枚举对象 UsingBlock vs. for

除了明显的区别:

  • 当您同时需要索引和对象时,请使用 enumerateObjectsUsingBlock
  • 当您需要修改本地变量 时,不要使用 enumerateObjectsUsingBlock(关于这一点我错了,请参阅 bbum 的答案)

for (id obj in myArray)也可以工作时,enumerateObjectsUsingBlock通常被认为是更好还是更差?优点/缺点是什么(例如它的性能是多还是少) ?

65526 次浏览

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

一个简单的循环也能读得更清楚,下面是两个版本的样板:

for (id x in y){
}


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

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

那么什么时候应该使用 enumerateObjectsUsingBlock:呢?当您存储一个要稍后执行或在多个位置执行的块时。当你实际上使用一个块作为第一类函数而不是一个过度紧张的替代品时,它是很好的。

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

虽然 for(... in ...)非常方便,在语法上也非常简短,但是 enumerateObjectsUsingBlock:有一些特性可能很有趣,也可能不太有趣:

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

  • “当需要修改本地变量时,不要使用枚举对象 UsingBlock”-不正确; 您可以将本地变量声明为 __block,并且它们可以在块中写入。

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

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

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

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

这些选择包括:

1) makeObjectsPerformSelector

[arr makeObjectsPerformSelector:@selector(_stubMethod)];

2)快速枚举及定期邮件发送

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

3)枚举对象使用块和定期消息发送

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

到目前为止,makeObjectsPerformSelector 是最慢的。它的时间是快速枚举的两倍。并且枚举对象使用块是最快的,它比快速迭代快15-20% 。

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

虽然这个问题已经很老了,但是事情并没有改变

enumerateObjectsUsingBlock API 并不是为了取代 for-in,而是为了完全不同的用例:

  • 它允许应用任意的非局部逻辑。也就是说,你不需要知道代码块做什么才能在数组中使用它。
  • 大型集合或大型计算的并发枚举(使用 withOptions:参数)

使用 for-in进行快速枚举仍然是枚举集合的 成语方法。

快速枚举得益于代码的简洁性、可读性和 额外的优化,这使得它异常快速。比老式的 C for-loop 还快!

一个快速测试得出结论,在2014年的 iOS7上,enumerateObjectsUsingBlock始终比 for-in 慢700% (基于100个项目数组的1毫米迭代)。

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

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

关键是要证明,如果没有很好的理由,在 for-in上使用 enumerateObjectsUsingBlock:几乎没有什么好处。它不会使代码更易读... 或者更快... 或者线程安全。(另一个常见的误解)。

选择取决于个人喜好。对我来说,惯用的和可读的选项获胜。在本例中,这是使用 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

当您想要中断嵌套循环时,使用枚举对象 UsingBlock 作为外部循环是相当有用的。

例如:。

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

感谢@bbum 和@Chuck 对性能进行了全面的比较。很高兴知道这是小事。我似乎是这么想的:

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

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

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