objective-c cocoa iteration nsarray enumeration

objective c - ¿Cómo itero sobre una NSArray?



objective-c cocoa (7)

Estoy buscando el idioma estándar para iterar sobre un NSArray. Mi código debe ser adecuado para OS X 10.4+.


Agrega each método en tu NSArray category , lo necesitarás mucho

Código tomado de ObjectiveSugar

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


Aquí es cómo declarar una serie de cadenas y iterar sobre ellos:

NSArray *langs = @[@"es", @"en", @"pt", @"it", @"fr"]; for (int i = 0; i < [langs count]; i++) { NSString *lang = (NSString*) [langs objectAtIndex:i]; NSLog(@"%@, ",lang); }


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 protocolo NSFastEnumeration . Este enfoque tiene una ventaja de velocidad porque almacena los punteros a varios objetos (obtenidos a través de una sola llamada de método) en un búfer y los itera avanzando a través del búfer usando aritmética de punteros. Esto es mucho más rápido que llamar a -objectAtIndex: cada vez que -objectAtIndex: por el bucle.

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

radar://6296108 esto en el radar://6296108 (la enumeración rápida de NSEnumerators es lenta) pero se devolvió como Not To Be Fixed. La razón es que la enumeración rápida precoge a un grupo de objetos, y si desea enumerar solo 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 separar 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 NSEnumerationConcurrent y / o NSEnumerationReverse como el argumento de las opciones.

10.4 o anterior

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

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

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

Cuando utiliza -objectEnumerator , puede cambiar fácilmente a otra colección enumerable (como un NSSet , claves en un NSDictionary , etc.), o incluso cambiar a -reverseObjectEnumerator para enumerar una matriz al revés, todo esto sin ningún otro cambio de código. Si el código de iteración está en un método, incluso podría pasar cualquier NSEnumerator y el código no tiene que preocuparse por lo que está iterando. Además, un NSEnumerator (al menos los proporcionados por el código de Apple) conserva 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 publicado automáticamente.

Quizás lo más importante de lo que un 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 está enumerando. Si accede a los objetos por índice, puede encontrarse con extrañas excepciones o errores off-by-one (a menudo mucho después de que se haya producido el problema) que pueden ser horribles 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 haya ocurrido 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! :-)


Hacer esto :-

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


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, olvídate de escribir el sobre de iteración.

Los resultados de la prueba y el código fuente se encuentran 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 a 3 dígitos significativos y que luego variaría con cada ejecución. Eso le da un margen de error de menos del 1%. La prueba se estaba ejecutando en un iPhone 3G, ya que esa es la plataforma objetivo en la que estaba interesado.

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 información, sin tener que preocuparse por la burocracia de la administración de la memoria, la reasignación, etc. Por supuesto, esto tiene un costo. . Creo que es bastante obvio que decir que usar una NSArray de NSNumbers va a ser más lento que una C Array de flotadores 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 objetivo en la que estaba interesado.

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

Creo un simple bucle para resumir los contenidos de cada matriz y los cronometro usando mach_absolute_time (). ¡El NSMutableArray tarda en promedio 400 veces más! (¡No 400 por ciento, solo 400 veces más largo! ¡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

Desde: 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 usar 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]; }

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

NSEnumerador

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

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

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

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


Para OS X 10.4.x y 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 }