Когда использовать enumerateObjectsUsingBlock или для - PullRequest
150 голосов
/ 20 декабря 2010

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

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

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

Ответы [ 6 ]

346 голосов
/ 20 декабря 2010

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

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

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

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

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

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

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

81 голосов
/ 20 декабря 2010

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

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

for (id x in y){
}

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

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

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

41 голосов
/ 11 июня 2014

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

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

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

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

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

Быстрый тест показывает, что в 2014 году на iOS 7 enumerateObjectsUsingBlock всегда на 700% медленнее, чем for-in (на основе итераций массива из 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
23 голосов
/ 05 февраля 2012

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

Варианты:

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

3 голосов
/ 28 августа 2015

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

1 голос
/ 30 декабря 2015

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

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

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

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

...