Это неэффективный способ использования быстрого перечисления?

Я не совсем понимаю детали того, как работает быстрое перечисление, но сравниваю следующие два случая:

for(NSObject *object in self.myParent.parentsParents.granfathersMother.cousin.unclesNephew.array) {
    // do something
}

против

NSArray *array = self.myParent.parentsParents.granfathersMother.cousin.unclesNephew.array;
for(NSObject *object in array) {
     // do something
 }

В первом примере будет ли он проходить через всю эту цепочку каждую итерацию, чтобы получить массив? Должен ли я использовать второй способ?

2 ответа

Решение

Я был на WWDC, когда Apple представила Fast Enumeration, и (насколько я помню) нам тогда сказали, что правый объект перемещается в темп. Кроме того, так должно быть, так как это работает:

for (идентификатор foo в [myCollection reverseObjectEnumerator])

Вы можете видеть, что коллекции, которые выполняют быстрое перечисление, принимают "Протокол быстрого перечисления" (NSFastEnumeration), который имеет один метод:

– countByEnumeratingWithState:objects:count:

Этот метод возвращает массив объектов C, который позволяет перечислению идти очень быстро, снова поддерживая одноразовое использование правой стороны.


Теперь, сказав все это, в настоящее время Apple советует разработчикам (в WWDC) использовать перечисление блоков, которое, как они утверждают, и быстрее, и генерирует меньше кода:

[myCollection enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
... your code
} ];

То, что мне нравится делать, это не использовать "id obj", а фактический тип (чтобы избежать приведения в блоке):

[myCollection enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL *stop)
{
... your code
} ];

Ни компилятор, ни анализатор (4.4) не жалуются, когда я делаю это.

Если вам нужно установить переменную вне этого метода, то вы должны сделать ее блочной переменной:

__block int foo = 0;
[myCollection enumerateObjectsUsingBlock:^(NSNumber *num, NSUInteger idx, BOOL *stop)
{
   foo = MAX(foo, [num integerValue]);
} ];

РЕДАКТИРОВАТЬ: в качестве пояснения, прямой ответ на ваш вопрос "нет", утверждение "self.myParent.parentsParents.granfathersMother.cousin.unclesNephew.array" оценивается один раз, а конечный объект сохраняется как временная переменная в стеке. Также вы можете использовать ту же технику с перечислениями блоков - оператор вычисляется один раз, а последний возвращаемый объект используется для перечисления.

__block int foo = 0;
[self.myParent.parentsParents.granfathersMother.cousin.unclesNephew.array enumerateObjectsUsingBlock:^(NSNumber *num, NSUInteger idx, BOOL *stop)
{
   foo = MAX(foo, [num integerValue]);
} ];

РЕДАКТИРОВАТЬ 2: Я нашел другую тему на SO, где обсуждалась эта же тема. Единственное, что я упустил в отношении перечисления блоков, это то, что вы можете указать, что они должны выполняться одновременно (или наоборот), используя немного более сложный метод:

enumerateObjectsWithOptions:usingBlock:

Поскольку устройства iOS получают все больше и больше ядер, это может быть большой победой в зависимости от того, что вы делаете.

Ответ @bbum на этот вопрос (и другие тоже) здесь.

Это, вероятно, зависит от компилятора (т.е. не определено). Если вас это беспокоит, то добавьте некоторый временной код и узнайте сами:

#import <sys/time.h>

static unsigned getTickCount()
{
    struct timeval tv;
    gettimeofday(&tv, 0);
    return (unsigned)((tv.tv_sec * 1000) + (tv.tv_usec / 1000));
}

...

unsigned startTime = getTickCount();
for(NSObject *object in self.myParent.parentsParents.granfathersMother.cousin.unclesNephew.array) {
    // do something
}
unsigned endTime = getTickCount();
NSLog(@"That took %umS", endTime - startTime);

Вам нужно будет иметь довольно большой массив, чтобы зарегистрировать что-либо выше 0,

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