Когда следует использовать enumerateObjectsUsingBlock vs. for

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

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

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

Ответ 1

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

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

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

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

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

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

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

Ответ 2

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

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

for (id x in y){
}

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

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

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

Ответ 3

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

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

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

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

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

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

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

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

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

Выбор сводится к личным предпочтениям. Для меня побеждает идиоматический и читаемый вариант. В этом случае это Fast Enumeration с помощью 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

Ответ 4

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

Возможные варианты:

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. Но имейте в виду, что в некоторых случаях время, необходимое для перечисления коллекции, затмевается к тому времени, когда требуется выполнить любой код, который вы хотите, чтобы каждый объект выполнялся.

Ответ 5

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

Ответ 6

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

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

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

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