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

Помимо очевидных отличий:

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

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

6 ответов

Решение

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

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

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

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

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

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

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

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

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

for (id x in y){
}

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

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

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

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

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

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

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

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

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

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

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

Дело в том, чтобы продемонстрировать, что использование 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

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

Варианты были:

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

Довольно полезно использовать 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 ...) - как мой по умолчанию goto. Более интуитивно понятный для меня, больше истории программирования здесь, чем любое реальное предпочтение - многоязычное повторное использование, меньше набирание текста для большинства структур данных благодаря автоматическому завершению IDE: P.

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

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

Другие вопросы по тегам