Когда использовать enumerateObjectsUsingBlock и для
кроме очевидных различий:
- использовать
enumerateObjectsUsingBlock
когда вам нужен и индекс и объект -
не используйте(Я ошибся в этом, см. ответ bbum)enumerateObjectsUsingBlock
когда вам нужно изменить локальные переменные
и enumerateObjectsUsingBlock
Как правило, считается лучше или хуже, когда for (id obj in myArray)
также будет работать? Каковы преимущества / недостатки (например, он более или менее эффективен)?
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:
overfor-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++)
- для массивов, когда мне нужно начать с ненулевого индекса