Estou procurando o idioma padrão para iterar sobre um NSArray. Meu código precisa ser adequado para o OS X 10.4+.
Estou procurando o idioma padrão para iterar sobre um NSArray. Meu código precisa ser adequado para o OS X 10.4+.
Respostas:
O código geralmente preferido para 10.5 + / iOS.
for (id object in array) {
// do something with object
}
Essa construção é usada para enumerar objetos em uma coleção em conformidade com o NSFastEnumeration
protocolo. Essa abordagem tem uma vantagem de velocidade porque armazena ponteiros para vários objetos (obtidos através de uma única chamada de método) em um buffer e itera através deles, avançando pelo buffer usando a aritmética do ponteiro. Isso é muito mais rápido do que ligar -objectAtIndex:
todas as vezes no loop.
Também é importante notar que, embora tecnicamente você possa usar um loop for-in para percorrer um NSEnumerator
, descobri que isso anula praticamente toda a vantagem de velocidade da enumeração rápida. O motivo é que a NSEnumerator
implementação padrão de -countByEnumeratingWithState:objects:count:
coloca apenas um objeto no buffer em cada chamada.
Eu relatei isso em radar://6296108
(A enumeração rápida de NSEnumerators é lenta), mas foi retornada como Não deve ser corrigido. O motivo é que a enumeração rápida busca previamente um grupo de objetos e se você deseja enumerar apenas um determinado ponto do enumerador (por exemplo, até que um objeto específico seja encontrado ou a condição seja atendida) e use o mesmo enumerador após a quebra do loop, muitas vezes seria o caso de vários objetos serem ignorados.
Se você estiver codificando para o OS X 10.6 / iOS 4.0 e superior, também terá a opção de usar APIs baseadas em bloco para enumerar matrizes e outras coleções:
[array enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
// do something with object
}];
Você também pode usar -enumerateObjectsWithOptions:usingBlock:
e passar NSEnumerationConcurrent
e / ou NSEnumerationReverse
como argumento de opções.
O idioma padrão para pré-10.5 é usar um NSEnumerator
loop while, como:
NSEnumerator *e = [array objectEnumerator];
id object;
while (object = [e nextObject]) {
// do something with object
}
Eu recomendo mantê-lo simples. Amarrar-se a um tipo de matriz é inflexível e o suposto aumento de velocidade do uso -objectAtIndex:
é insignificante para a melhoria com a enumeração rápida em 10.5 ou superior. (A enumeração rápida realmente usa aritmética de ponteiro na estrutura de dados subjacente e remove a maior parte da sobrecarga de chamada do método.) Otimização prematura nunca é uma boa idéia - resulta em código mais confuso para resolver um problema que não é seu gargalo.
Ao usar -objectEnumerator
, você muda muito facilmente para outra coleção enumerável (como NSSet
, chaves em um NSDictionary
etc.) ou muda -reverseObjectEnumerator
para enumerar uma matriz de trás para frente, tudo sem outras alterações de código. Se o código de iteração estiver em um método, você pode até mesmo passar algum NSEnumerator
e o código nem precisa se preocupar com o que está iterando. Além disso, um NSEnumerator
(pelo menos aqueles fornecidos pelo código da Apple) retém a coleção que está enumerando, desde que haja mais objetos, para que você não precise se preocupar por quanto tempo um objeto liberado automaticamente existirá.
Talvez a maior coisa que uma NSEnumerator
(ou enumeração rápida) proteja você seja ter uma coleção mutável (matriz ou outra) alterada abaixo de você sem o seu conhecimento enquanto você a enumera. Se você acessar os objetos por índice, poderá encontrar estranhas exceções ou erros pontuais (geralmente muito tempo após o problema) que podem ser horríveis para depuração. A enumeração usando um dos idiomas padrão possui um comportamento "fail-fast"; portanto, o problema (causado por código incorreto) se manifestará imediatamente quando você tentar acessar o próximo objeto após a mutação. À medida que os programas ficam mais complexos e com vários threads, ou até mesmo dependem de algo que o código de terceiros pode modificar, o código de enumeração frágil se torna cada vez mais problemático. Encapsulamento e abstração FTW! :-)
for (id object in array)
, existe uma maneira de determinar também o índice atual de objetos na matriz ou um contador separado precisa ser incluído?
for
loop como este:for(;;) { id object = [ e nextObject ] ; if ( !e ) { break ; } ... your loop operation ... }
Para OS X 10.4.xe anterior:
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 (ou iPhone) e além:
for (id myArrayElement in myArray) {
...do something useful with myArrayElement
}
for (NSUInteger i = 0, count = [myArray count]; i < count; i++)
é provavelmente o mais eficiente e conciso que você terá para essa abordagem.
Os resultados do teste e do código-fonte estão abaixo (você pode definir o número de iterações no aplicativo). O tempo é em milissegundos e cada entrada é um resultado médio da execução do teste 5 a 10 vezes. Descobri que geralmente é preciso ter de 2 a 3 dígitos significativos e depois variaria a cada execução. Isso dá uma margem de erro inferior a 1%. O teste estava sendo executado em um iPhone 3G, pois essa é a plataforma de destino em que eu estava interessado.
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
As classes fornecidas pelo Cocoa para lidar com conjuntos de dados (NSDictionary, NSArray, NSSet etc.) fornecem uma interface muito agradável para gerenciar informações, sem ter que se preocupar com a burocracia de gerenciamento de memória, realocação etc. É claro que isso tem um custo . Eu acho que é bastante óbvio dizer que o uso de um NSArray de NSNumbers será mais lento que um C Array de flutuadores para iterações simples, então decidi fazer alguns testes e os resultados foram bastante chocantes! Eu não esperava que fosse tão ruim assim. Nota: esses testes são realizados em um iPhone 3G, pois essa é a plataforma de destino em que eu estava interessado.
Neste teste, faço uma comparação de desempenho de acesso aleatório muito simples entre um float C * e o NSArray of NSNumbers
Crio um loop simples para resumir o conteúdo de cada array e cronometrá-los usando mach_absolute_time (). O NSMutableArray leva em média 400 vezes mais! (não 400%, apenas 400 vezes mais! isso é 40.000% mais!).
Cabeçalho:
// Array_Speed_TestViewController.h
// Teste de velocidade da matriz
// Criado por Mehmet Akten em 05/02/2009.
// Copyright MSA Visuals Ltd. 2009. Todos os direitos 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
Implementação:
// Array_Speed_TestViewController.m
// Teste de velocidade da matriz
// Criado por Mehmet Akten em 05/02/2009.
// Copyright MSA Visuals Ltd. 2009. Todos os direitos 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
////////////////////
Disponível desde a introdução dos blocos, isso permite iterar uma matriz com blocos. Sua sintaxe não é tão boa quanto a enumeração rápida, mas há um recurso muito interessante: enumeração simultânea. Se a ordem de enumeração não for importante e os trabalhos puderem ser feitos em paralelo sem travar, isso poderá fornecer uma aceleração considerável em um sistema com vários núcleos. Mais sobre isso na seção de enumeração simultânea.
[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
A idéia por trás da enumeração rápida é usar o acesso rápido à matriz C para otimizar a iteração. Não é apenas mais rápido que o NSEnumerator tradicional, mas o Objective-C 2.0 também fornece uma sintaxe muito concisa.
id object;
for (object in myArray) {
[self doSomethingWith:object];
}
///////////////////
NSEnumerator
Esta é uma forma de iteração externa: [myArray objectEnumerator] retorna um objeto. Este objeto tem um método nextObject que podemos chamar em um loop até que retorne nulo
NSEnumerator *enumerator = [myArray objectEnumerator];
id object;
while (object = [enumerator nextObject]) {
[self doSomethingWith:object];
}
///////////////////
objectAtIndex: enumeração
Usar um loop for que aumenta um número inteiro e consultar o objeto usando [myArray objectAtIndex: index] é a forma mais básica de enumeração.
NSUInteger count = [myArray count];
for (NSUInteger index = 0; index < count ; index++) {
[self doSomethingWith:[myArray objectAtIndex:index]];
}
////////////// De: darkdust.net
As três maneiras são:
//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
}];
Adicione each
método no seu NSArray category
, você precisará muito
Código extraído do ObjectiveSugar
- (void)each:(void (^)(id object))block {
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
block(obj);
}];
}
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
}
Faça isso :-
for (id object in array)
{
// statement
}