¿Cómo itero sobre un NSArray?

Respuestas:

667

El código generalmente preferido para 10.5 + / iOS.

for (id object in array) {
    // do something with object
}

Esta construcción se utiliza para enumerar objetos en una colección que se ajusta al NSFastEnumerationprotocolo. Este enfoque tiene una ventaja de velocidad porque almacena punteros a varios objetos (obtenidos mediante una sola llamada a un método) en un búfer y los itera avanzando a través del búfer utilizando la aritmética del puntero. Esto es mucho más rápido que llamar -objectAtIndex:cada vez a través del ciclo.

También vale la pena señalar que, si bien técnicamente puede usar un bucle for-in para recorrer un paso NSEnumerator, he descubierto que esto anula prácticamente todas las ventajas de velocidad de la enumeración rápida. La razón es que la NSEnumeratorimplementación predeterminada de -countByEnumeratingWithState:objects:count:coloca solo un objeto en el búfer en cada llamada.

radar://6296108Informé esto en (La enumeración rápida de NSEnumerators es lenta) pero se devolvió como No se debe corregir. La razón es que la enumeración rápida busca previamente un grupo de objetos, y si desea enumerar solo en un punto dado en el enumerador (por ejemplo, hasta que se encuentre un objeto en particular o se cumpla una condición) y use el mismo enumerador después de dividir del bucle, a menudo sería el caso de que se omitieran varios objetos.

Si está codificando para OS X 10.6 / iOS 4.0 y superior, también tiene la opción de usar API basadas en bloques para enumerar matrices y otras colecciones:

[array enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
    // do something with object
}];

También puede usar -enumerateObjectsWithOptions:usingBlock:y pasar NSEnumerationConcurrenty / o NSEnumerationReversecomo argumento de opciones.


10.4 o anterior

El idioma estándar para pre-10.5 es usar un NSEnumeratorbucle while y un while, así:

NSEnumerator *e = [array objectEnumerator];
id object;
while (object = [e nextObject]) {
  // do something with object
}

Recomiendo que sea simple. Atarse a un tipo de matriz es inflexible, y el supuesto aumento de velocidad de uso -objectAtIndex:es insignificante para la mejora con una enumeración rápida en 10.5+ de todos modos. (La enumeración rápida en realidad usa la aritmética de puntero en la estructura de datos subyacente, y elimina la mayor parte de la sobrecarga de llamadas al método). La optimización prematura nunca es una buena idea: resulta en un código más desordenado para resolver un problema que de todos modos no es su cuello de botella.

Al usarlo -objectEnumerator, puede cambiar fácilmente a otra colección enumerable (como an NSSet, keys in an NSDictionary, etc.), o incluso cambiar -reverseObjectEnumeratora enumerar una matriz al revés, todo sin otros cambios de código. Si el código de iteración está en un método, incluso podría pasar cualquiera NSEnumeratory el código ni siquiera tiene que preocuparse por lo que está iterando. Además, un NSEnumerator(al menos los proporcionados por el código de Apple) retiene la colección que está enumerando siempre que haya más objetos, por lo que no tiene que preocuparse por cuánto tiempo existirá un objeto lanzado automáticamente.

Quizás lo más importante de lo que una NSEnumerator(o enumeración rápida) lo protege es tener un cambio de colección mutable (matriz o de otro tipo) debajo de usted sin su conocimiento mientras lo enumera. Si accede a los objetos por índice, puede encontrarse con extrañas excepciones o errores ocasionales (a menudo mucho después de que se haya producido el problema) que pueden ser terribles de depurar. La enumeración que usa uno de los modismos estándar tiene un comportamiento de "falla rápida", por lo que el problema (causado por un código incorrecto) se manifestará de inmediato cuando intente acceder al siguiente objeto después de que se haya producido la mutación. A medida que los programas se vuelven más complejos y multiproceso, o incluso dependen de algo que el código de terceros puede modificar, el código de enumeración frágil se vuelve cada vez más problemático. Encapsulación y abstracción FTW! :-)


Quinn Taylor
fuente
28
Nota: la mayoría de los compiladores darán una advertencia sobre "while (object = [e nextObject])". En este caso, en realidad SÍ quieres usar = en lugar de ==. Para suprimir las advertencias, puede agregar paréntesis adicionales: "while ((object = [e nextObject]))".
Adam Rosenfield
Otra cosa para recordar con NSEnumerator es que usará más memoria (hace una copia de la matriz) que el enfoque objectWithIndex.
diederikh
@QuinnTaylor Usando for (id object in array), ¿hay alguna manera de determinar también el índice actual de los objetos en la matriz, o necesita un contador separado?
Coderama
1
Necesitaría un contador separado, pero sugeriría considerar la enumeración basada en bloques (que incluye el índice) si está disponible para usted.
Quinn Taylor
Soy raro, pero uso un forbucle como este:for(;;) { id object = [ e nextObject ] ; if ( !e ) { break ; } ... your loop operation ... }
nielsbot
125

Para OS X 10.4.xy versiones anteriores:

 int i;
 for (i = 0; i < [myArray count]; i++) {
   id myArrayElement = [myArray objectAtIndex:i];
   ...do something useful with myArrayElement
 }

Para OS X 10.5.x (o iPhone) y más allá:

for (id myArrayElement in myArray) {
   ...do something useful with myArrayElement
}
diederikh
fuente
2
En el primer ejemplo, ni siquiera tiene que declarar el int fuera del ciclo. Esto funciona igual de bien, y abarca muy bien la variable para que pueda reutilizarla más adelante si es necesario: for (int i = 0; i <[myArray count]; i ++) ... Pero también, tenga en cuenta que llamar -count cada vez a través de la matriz puede cancelar el beneficio de usar -objectAtIndex:
Quinn Taylor
1
Y en realidad, si está enumerando una colección mutable, es técnicamente más correcto verificar el recuento cada vez que se realiza el ciclo. Aclaré mi respuesta para explicar que usar NSEnumerator o NSFastEnumeration puede proteger contra las mutaciones concurrentes de la matriz.
Quinn Taylor
La construcción for (int i = 0; ...) es un dialecto en lenguaje C (C99 es creer), que yo mismo uso pero no estaba seguro de que fuera el código XCode predeterminado.
diederikh
C99 es el predeterminado hasta Xcode 3.1.x: en algún momento futuro, el predeterminado cambiará a GNU99, que (entre otras cosas) admite uniones anónimas y estructuras. Eso debería ser agradable ...
Quinn Taylor
2
El uso for (NSUInteger i = 0, count = [myArray count]; i < count; i++)es probablemente el más eficiente y conciso que obtendrá para este enfoque.
Quinn Taylor
17

Los resultados de la prueba y el código fuente están a continuación (puede establecer el número de iteraciones en la aplicación). El tiempo es en milisegundos, y cada entrada es un resultado promedio de ejecutar la prueba 5-10 veces. Descubrí que, en general, tiene una precisión de 2-3 dígitos significativos y después de eso variaría con cada ejecución. Eso da un margen de error de menos del 1%. La prueba se ejecutaba en un iPhone 3G, ya que esa era la plataforma de destino que me interesaba.

numberOfItems   NSArray (ms)    C Array (ms)    Ratio
100             0.39            0.0025          156
191             0.61            0.0028          218
3,256           12.5            0.026           481
4,789           16              0.037           432
6,794           21              0.050           420
10,919          36              0.081           444
19,731          64              0.15            427
22,030          75              0.162           463
32,758          109             0.24            454
77,969          258             0.57            453
100,000         390             0.73            534

Las clases proporcionadas por Cocoa para manejar conjuntos de datos (NSDictionary, NSArray, NSSet, etc.) proporcionan una interfaz muy agradable para administrar la información, sin tener que preocuparse por la burocracia de la administración de la memoria, la reasignación, etc. Sin embargo, esto tiene un costo. . Creo que es bastante obvio que decir que usar un NSArray of NSNumbers será más lento que un C Array de flotantes para iteraciones simples, así que decidí hacer algunas pruebas, ¡y los resultados fueron bastante impactantes! No esperaba que fuera tan malo. Nota: estas pruebas se realizan en un iPhone 3G, ya que esa es la plataforma de destino que me interesaba.

En esta prueba, hago una comparación de rendimiento de acceso aleatorio muy simple entre un flotante C * y NSArray of NSNumbers

Creo un bucle simple para resumir el contenido de cada matriz y cronometrarlos usando mach_absolute_time (). ¡El NSMutableArray tarda en promedio 400 veces más! (¡no 400 por ciento, solo 400 veces más! ¡eso es 40,000% más!).

Encabezamiento:

// Array_Speed_TestViewController.h

// Prueba de velocidad de matriz

// Creado por Mehmet Akten el 05/02/2009.

// Copyright MSA Visuals Ltd. 2009. Todos los derechos reservados.

#import <UIKit/UIKit.h>

@interface Array_Speed_TestViewController : UIViewController {

    int                     numberOfItems;          // number of items in array

    float                   *cArray;                // normal c array

    NSMutableArray          *nsArray;               // ns array

    double                  machTimerMillisMult;    // multiplier to convert mach_absolute_time() to milliseconds



    IBOutlet    UISlider    *sliderCount;

    IBOutlet    UILabel     *labelCount;


    IBOutlet    UILabel     *labelResults;

}


-(IBAction) doNSArray:(id)sender;

-(IBAction) doCArray:(id)sender;

-(IBAction) sliderChanged:(id)sender;


@end

Implementación:

// Array_Speed_TestViewController.m

// Prueba de velocidad de matriz

// Creado por Mehmet Akten el 05/02/2009.

// Copyright MSA Visuals Ltd. 2009. Todos los derechos reservados.

    #import "Array_Speed_TestViewController.h"
    #include <mach/mach.h>
    #include <mach/mach_time.h>

 @implementation Array_Speed_TestViewController



 // Implement viewDidLoad to do additional setup after loading the view, typically from a nib.

- (void)viewDidLoad {

    NSLog(@"viewDidLoad");


    [super viewDidLoad];


    cArray      = NULL;

    nsArray     = NULL;


    // read initial slider value setup accordingly

    [self sliderChanged:sliderCount];


    // get mach timer unit size and calculater millisecond factor

    mach_timebase_info_data_t info;

    mach_timebase_info(&info);

    machTimerMillisMult = (double)info.numer / ((double)info.denom * 1000000.0);

    NSLog(@"machTimerMillisMult = %f", machTimerMillisMult);

}



// pass in results of mach_absolute_time()

// this converts to milliseconds and outputs to the label

-(void)displayResult:(uint64_t)duration {

    double millis = duration * machTimerMillisMult;


    NSLog(@"displayResult: %f milliseconds", millis);


    NSString *str = [[NSString alloc] initWithFormat:@"%f milliseconds", millis];

    [labelResults setText:str];

    [str release];

}




// process using NSArray

-(IBAction) doNSArray:(id)sender {

    NSLog(@"doNSArray: %@", sender);


    uint64_t startTime = mach_absolute_time();

    float total = 0;

    for(int i=0; i<numberOfItems; i++) {

        total += [[nsArray objectAtIndex:i] floatValue];

    }

    [self displayResult:mach_absolute_time() - startTime];

}




// process using C Array

-(IBAction) doCArray:(id)sender {

    NSLog(@"doCArray: %@", sender);


    uint64_t start = mach_absolute_time();

    float total = 0;

    for(int i=0; i<numberOfItems; i++) {

        total += cArray[i];

    }

    [self displayResult:mach_absolute_time() - start];

}



// allocate NSArray and C Array 

-(void) allocateArrays {

    NSLog(@"allocateArrays");


    // allocate c array

    if(cArray) delete cArray;

    cArray = new float[numberOfItems];


    // allocate NSArray

    [nsArray release];

    nsArray = [[NSMutableArray alloc] initWithCapacity:numberOfItems];



    // fill with random values

    for(int i=0; i<numberOfItems; i++) {

        // add number to c array

        cArray[i] = random() * 1.0f/(RAND_MAX+1);


        // add number to NSArray

        NSNumber *number = [[NSNumber alloc] initWithFloat:cArray[i]];

        [nsArray addObject:number];

        [number release];

    }


}



// callback for when slider is changed

-(IBAction) sliderChanged:(id)sender {

    numberOfItems = sliderCount.value;

    NSLog(@"sliderChanged: %@, %i", sender, numberOfItems);


    NSString *str = [[NSString alloc] initWithFormat:@"%i items", numberOfItems];

    [labelCount setText:str];

    [str release];


    [self allocateArrays];

}



//cleanup

- (void)dealloc {

    [nsArray release];

    if(cArray) delete cArray;


    [super dealloc];

}


@end

De: memo.tv

////////////////////

Disponible desde la introducción de bloques, esto permite iterar una matriz con bloques. Su sintaxis no es tan buena como la enumeración rápida, pero hay una característica muy interesante: la enumeración concurrente. Si el orden de enumeración no es importante y los trabajos se pueden realizar en paralelo sin bloqueo, esto puede proporcionar una aceleración considerable en un sistema de múltiples núcleos. Más sobre eso en la sección de enumeración concurrente.

[myArray enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
    [self doSomethingWith:object];
}];
[myArray enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    [self doSomethingWith:object];
}];

/////////// NSFastEnumerator

La idea detrás de la enumeración rápida es utilizar el acceso rápido a la matriz C para optimizar la iteración. No solo se supone que es más rápido que el NSEnumerator tradicional, sino que Objective-C 2.0 también proporciona una sintaxis muy concisa.

id object;
for (object in myArray) {
    [self doSomethingWith:object];
}

/////////////////

NSEnumerator

Esta es una forma de iteración externa: [myArray objectEnumerator] devuelve un objeto. Este objeto tiene un método nextObject que podemos llamar en un bucle hasta que devuelva nil

NSEnumerator *enumerator = [myArray objectEnumerator];
id object;
while (object = [enumerator nextObject]) {
    [self doSomethingWith:object];
}

/////////////////

objectAtIndex: enumeración

Usar un bucle for que aumenta un número entero y consultar el objeto usando [myArray objectAtIndex: index] es la forma más básica de enumeración.

NSUInteger count = [myArray count];
for (NSUInteger index = 0; index < count ; index++) {
    [self doSomethingWith:[myArray objectAtIndex:index]];
}

////////////// De: darkdust.net

Hitendra Solanki
fuente
12

Las tres formas son:

        //NSArray
    NSArray *arrData = @[@1,@2,@3,@4];

    // 1.Classical
    for (int i=0; i< [arrData count]; i++){
        NSLog(@"[%d]:%@",i,arrData[i]);
    }

    // 2.Fast iteration
    for (id element in arrData){
        NSLog(@"%@",element);
    }

    // 3.Blocks
    [arrData enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
         NSLog(@"[%lu]:%@",idx,obj);
         // Set stop to YES in case you want to break the iteration
    }];
  1. Es la forma más rápida de ejecución, y 3. con autocompletar olvidarse de escribir el sobre de iteración.
Javier Calatrava Llavería
fuente
7

Agregue eachmétodo en su NSArray category, lo necesitará mucho

Código tomado de ObjectiveSugar

- (void)each:(void (^)(id object))block {
    [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        block(obj);
    }];
}
onmyway133
fuente
5

Así es como declaras una serie de cadenas e iteras sobre ellas:

NSArray *langs = @[@"es", @"en", @"pt", @"it", @"fr"];

for (int i = 0; i < [langs count]; i++) {
  NSString *lang = (NSString*) [langs objectAtIndex:i];
  NSLog(@"%@, ",lang);
}
oabarca
fuente
0

Para Swift

let arrayNumbers = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

// 1
for (index, value) in arrayNumbers.enumerated() {
    print(index, value)
    //... do somthing with array value and index
}


//2
for value in arrayNumbers {
    print(value)
    //... do somthing with array value
}
Nilesh R Patel
fuente
-1

Hacer esto :-

for (id object in array) 
{
        // statement
}

fuente