Когда использовать enumerateObjectsUsingBlock и для


кроме очевидных различий:

  • использовать enumerateObjectsUsingBlock когда вам нужен и индекс и объект
  • не используйте enumerateObjectsUsingBlock когда вам нужно изменить локальные переменные (Я ошибся в этом, см. ответ bbum)

и enumerateObjectsUsingBlock Как правило, считается лучше или хуже, когда for (id obj in myArray) также будет работать? Каковы преимущества / недостатки (например, он более или менее эффективен)?

6 148

6 ответов:

в конечном счете, используйте тот шаблон, который вы хотите использовать, и более естественно в контексте.

пока for(... in ...) довольно удобно и синтаксически кратко,enumerateObjectsUsingBlock: имеет ряд особенностей, которые могут или не могут оказаться интересными:

  • enumerateObjectsUsingBlock: будет так же быстро или быстрее, чем быстрое перечисление (for(... in ...) использует NSFastEnumeration поддержка реализации перечисления). Быстрое перечисление требует перевода из внутреннего представления в представление для быстрого перечисления. Там есть накладные расходы. Перечисление на основе блоков позволяет классу коллекции перечислять содержимое так же быстро, как и самый быстрый обход собственного формата хранения. Вероятно, не имеет значения для массивов, но это может быть огромная разница для словарей.

  • "не используйте enumerateObjectsUsingBlock, когда вам нужно изменить локальные переменные" - не верно; вы можете объявить свои локальные значения как __block и они будут доступны для записи в блок.

  • enumerateObjectsWithOptions:usingBlock: поддерживает параллельное или обратное перечисление.

  • С помощью словарей перечисление на основе блоков является единственным способом получения ключа и значения одновременно.

лично я использую enumerateObjectsUsingBlock: чаще, чем for (... in ...), но - опять же - личный выбор.

для простого перечисления, просто используя быстрое перечисление (т. е. a for…in… loop) является более идиоматическим вариантом. Блочный метод может быть немного быстрее, но в большинстве случаев это не имеет большого значения - несколько программ связаны с процессором, и даже тогда редко бывает, что сам цикл, а не вычисление внутри будет узким местом.

простой цикл также читается более четко. Вот шаблон двух версий:

for (id x in y){
}

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

даже если вы добавляете переменную отслеживание индекса, простой цикл легче читать.

Итак, когда вы должны использовать enumerateObjectsUsingBlock:? Когда вы храните блок для выполнения позже или в нескольких местах. Это хорошо, когда вы на самом деле используете блок в качестве первоклассной функции, а не перегруженной заменой тела цикла.

хотя этот вопрос старый, вещи не изменились,принятый ответ неверен.

The enumerateObjectsUsingBlock API не должен был заменить for-in, но для совершенно другого дела использовать:

  • это позволяет применять произвольную, нелокальную логику. т. е. вам не нужно знать, что происходит, чтобы использовать его на массив.
  • параллельное перечисление для больших коллекций или тяжелых вычислений (с помощью withOptions: параметр)

быстрое перечисление с for-in по-прежнему идиоматическое метод перечисления коллекции.

преимущества быстрого перечисления от краткости кода, удобочитаемости и дополнительные оптимизации что делает его неестественно быстрым. Быстрее, чем старый C for-loop!

быстрый тест делает вывод, что в 2014 году на iOS 7, enumerateObjectsUsingBlock последовательно на 700% медленнее, чем for-in (на основе 1 мм итераций 100 элементов матрица.)

является ли производительность реальной практической проблемой здесь?

однозначно нет, за редким исключением.

дело в том, чтобы продемонстрировать, что есть мало пользы от использования enumerateObjectsUsingBlock: over for-in без действительно веской причины. Это не делает код более читаемым... или быстрее... или потокобезопасный. (еще одно распространенное заблуждение).

выбор сводится к личным предпочтениям. Для меня, идиоматический и читаемый вариант выигрывает. В этом случае это быстрое перечисление с помощью for-in.

Benchmark:

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

чтобы ответить на вопрос о производительности, я сделал несколько тестов, используя мой тест производительности проекта. Я хотел знать, какой из трех вариантов отправки сообщения всем объектам в массиве является самым быстрым.

варианты:

1) makeObjectsPerformSelector

[arr makeObjectsPerformSelector:@selector(_stubMethod)];

2) Быстрое перечисление и регулярное сообщение отправить

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

3) enumerateObjectsUsingBlock & regular отправить сообщение

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

оказывается, что makeObjectsPerformSelector был самым медленным на сегодняшний день. Это заняло в два раза больше времени, чем быстрое перечисление. И enumerateObjectsUsingBlock был самым быстрым, он был примерно на 15-20% быстрее, чем быстрая итерация.

поэтому, если вы очень обеспокоены лучшей возможной производительностью, используйте enumerateObjectsUsingBlock. Но имейте в виду, что в некоторых случаях время, необходимое для перечисления коллекции, затмевает время, необходимое для запуска любой код, который вы хотите, чтобы каждый объект выполнялся.

довольно полезно использовать 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.

спасибо @bbum и @Chuck для начала всесторонних сравнений по производительности. Рад узнать, что это тривиально. Я, кажется, пошел с:

  • for (... in ...) - Как мне перейти по умолчанию. Более интуитивный для меня, больше истории программирования здесь, чем любые реальные предпочтения - перекрестное повторное использование языка, меньше ввода для большинства структур данных из-за автоматического завершения IDE :P.

  • enumerateObject... - когда требуется доступ к объекту и индексу. И при доступе к не-массиву или словарные структуры (личные предпочтения)

  • for (int i=idx; i<count; i++) - для массивов, когда мне нужно начать с ненулевого индекса