【问题标题】:When to use enumerateObjectsUsingBlock vs. for何时使用 enumerateObjectsUsingBlock 与 for
【发布时间】:2011-05-28 01:29:40
【问题描述】:

除了明显的区别:

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

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

【问题讨论】:

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


【解决方案1】:

虽然这个问题很老,但事情并没有改变,接受的答案是不正确的。

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

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

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

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

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

性能是一个真正的实际问题吗?

绝对不是,除了极少数例外。

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

【讨论】:

  • 我可以确认在 MacBook Pro Retina 2014 上运行相同的测试,enumerateObjectsUsingBlock 实际上慢了 5 倍。这显然是由于自动释放池包装了块的每次调用,for in 情况不会发生这种情况。
  • 我确认enumerateObjectsUsingBlock: 在真正的 iPhone 6 iOS9 上仍然慢 4 倍,使用 Xcode 7.x 构建。
  • 感谢您的确认!我希望这个答案没有被埋没……有些人只是喜欢enumerate 语法,因为它感觉像 FP,不想听性能分析。
  • 顺便说一句,这不仅仅是由于自动释放池。 enumerate: 有更多的堆栈推送和弹出
【解决方案2】:

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

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

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

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

【讨论】:

    【解决方案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 语句。

    【讨论】:

    • 或者你可以像你在这里所做的那样从方法中返回:-D
    【解决方案4】:

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

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

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

    • “当你需要修改局部变量时不要使用 enumerateObjectsUsingBlock”——不正确;你可以将你的本地人声明为__block,它们将在块中是可写的。

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

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

    就个人而言,我使用enumerateObjectsUsingBlock: 的频率高于for (... in ...),但 - 再次 - 个人选择。

    【讨论】:

    • 哇,信息量很大。我希望我能接受这两个答案,但我会选择 Chuck 的,因为它更能引起我的共鸣。另外,我在搜索 __block 时发现了您的博客 (friday.com/bbum/2009/08/29/blocks-tips-tricks),并且学到了更多。谢谢。
    • 为了记录,基于块的枚举并不总是“一样快或更快”mikeabdullah.net/slow-block-based-dictionary-enumeration.html
    • @VanDuTran 块仅在您告诉它们在单独的线程上执行时才在单独的线程上执行。除非您使用枚举的并发选项,否则它将在与调用相同的线程上执行
    • Nick Lockwood 在这方面做了一篇非常好的文章,看起来enumerateObjectsUsingBlock 仍然比数组和集合的快速枚举慢得多。我想知道为什么? iosdevelopertips.com/objective-c/…
    • 虽然它是一种实现细节,但应该在这个答案中提到enumerateObjectsUsingBlock 使用自动释放池包装块的每次调用的两种方法之间的差异(从 OS X 10.10 开始)至少)。这解释了与不这样做的for in 相比的性能差异。
    【解决方案5】:

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

    选项是:

    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。但请记住,在某些情况下,枚举集合所需的时间与运行您希望每个对象执行的任何代码所需的时间相比相形见绌。

    【讨论】:

    • 你能指出你的具体测试吗?您似乎给出了错误的答案。
    【解决方案6】:

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

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

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

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

    那么什么时候应该使用enumerateObjectsUsingBlock:?当您存储块以稍后或在多个位置执行时。当您实际使用块作为一等函数而不是过度替换循环体时,这很有用。

    【讨论】:

    • enumerateObjectsUsingBlock: 在所有情况下都将比快速枚举的速度相同或更快。 for(... in ...) 使用快速枚举,这需要集合提供内部数据结构的一些临时表示。如您所述,可能无关紧要。
    • +1 When 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.
    • @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。奇怪的是,时间差如此之大且一致,但显然,这是一个非常具体的测试用例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-25
    • 2014-08-04
    • 1970-01-01
    • 1970-01-01
    • 2022-01-12
    • 1970-01-01
    相关资源
    最近更新 更多