Je suis à la recherche de la norme de l'idiome d'itérer sur un NSArray. Mon code doit être adapté pour OS X 10.4+.
Réponses
Trop de publicités?Généralement préféré de code pour 10.5+/iOS.
for (id object in array) {
// do something with object
}
Cette construction est utilisée pour énumérer les objets dans une collection qui est conforme à l' [NSFastEnumeration protocol]
(Cacao de Référence). Cette approche a un avantage de vitesse, car il stocke des pointeurs à plusieurs objets (obtenu via un seul appel de méthode) dans un tampon et parcourt eux en avançant dans la mémoire tampon à l'aide de l'arithmétique des pointeurs. C'est beaucoup plus rapide que d'appeler -objectAtIndex:
chaque passage dans la boucle.
Il est également intéressant de noter que, bien que vous techniquement pouvez utiliser un pour-en boucle à l'étape par le biais d'un NSEnumerator
, j'ai trouvé que cela annule pratiquement la totalité de l'avantage en termes de vitesse de rapide énumération. La raison en est que la valeur par défaut NSEnumerator
de la mise en œuvre de l' -countByEnumeratingWithState:objects:count:
endroits seulement un objet dans la mémoire tampon sur chaque appel.
J'ai signalé ce en radar://6296108
(Rapide énumération de NSEnumerators est très lent), mais il a été renvoyé De ne Pas Être Fixe. La raison en est que rapide énumération pré-extrait un groupe d'objets, et si vous souhaitez énumérer seulement à un point donné dans l'énumérateur (p. ex. jusqu'à un objet particulier est trouvé, ou si la condition est remplie) et d'utiliser le même agent recenseur après la rupture de la boucle, il sera souvent le cas que plusieurs objets ignorées.
Si vous êtes le codage pour OS X 10.6 / iOS 4.0 et ci-dessus, vous avez également la possibilité d'utiliser la fonction de bloc Api pour énumérer les tableaux et d'autres collections:
[array enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
// do something with object
}];
Vous pouvez également utiliser -enumerateObjectsWithOptions:usingBlock:
et passent NSEnumerationConcurrent
et/ou NSEnumerationReverse
que l'argument options.
10.4 ou version antérieure
La norme idiome pour pré-10.5 est d'utiliser un NSEnumerator
et une boucle while, comme suit:
NSEnumerator *e = [array objectEnumerator];
id object;
while (object = [e nextObject]) {
// do something with object
}
Je recommande de garder les choses simples. Vous attacher à un type tableau est inflexible, et la prétendue augmentation de la vitesse de l'aide de -objectAtIndex:
est négligeable à l'amélioration rapide de l'énumération 10.5+ de toute façon. (Rapide énumération utilise l'arithmétique des pointeurs sur les données sous-jacentes de la structure, et supprime la plupart de l'appel de la méthode de surcharge.) L'optimisation prématurée est jamais une bonne idée, il en résulte messier code pour résoudre un problème qui n'est pas le goulot d'étranglement de toute façon.
Lors de l'utilisation d' -objectEnumerator
, vous pouvez facilement passer à un autre énumérable collection (comme un NSSet
, clés en NSDictionary
, etc.), ou même passer à l' -reverseObjectEnumerator
d'énumérer un tableau à l'envers, tous n'ayant pas d'autres changements dans le code. Si l'itération code dans une méthode, vous pouvez même passer dans tous NSEnumerator
et le code n'a même pas besoin de s'inquiéter de ce que c'est de l'itération. De plus, une NSEnumerator
(au moins ceux fournis par Apple code) conserve la collection, il est l'énumération de tant qu'il y a plus d'objets, de sorte que vous n'avez pas à vous soucier de combien de temps un autoreleased objet existera.
Peut-être la plus grande chose qu'un NSEnumerator
(ou rapide énumération) vous protège contre c'est d'avoir une mutable collection (tableau ou autre) changement en dessous de vous , à votre insu, alors que vous êtes de l'énumération. Si vous accédez à des objets par index, vous pouvez exécuter dans d'étranges exceptions ou off-by-one erreurs (souvent longtemps après que le problème a eu lieu) qui peut être effrayant de débogage. Dénombrement à l'aide de l'une des expressions idiomatiques a un "fail-fast" comportement, de sorte que le problème (causée par un mauvais code) va se manifester immédiatement lorsque vous essayez d'accéder à l'objet suivant après la mutation a eu lieu. Comme les programmes deviennent plus complexes et multi-thread, ou même dépend de quelque chose que la troisième partie du code peut modifier, fragile énumération code devient de plus en plus problématique. Encapsulation et d'abstraction FTW! :-)
Les résultats du test et le code source sont ci-dessous (vous pouvez définir le nombre d'itérations de l'application). Le temps est exprimé en millisecondes, et chaque entrée est une moyenne des résultats de l'exécution du test de 5 à 10 fois. J'ai trouvé que, généralement, il est précis à 2 ou 3 chiffres significatifs et après qu'il varie avec chaque exécution. Qui donne une marge d'erreur de moins de 1%. Le test a été exécuté sur un iPhone 3G que c'est la plate-forme cible qui m'intéressait.
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
Les classes fournies par le Cacao pour la manipulation de jeux de données (NSDictionary, NSArray, NSSet etc.) fournir une très belle interface pour la gestion de l'information, sans avoir à vous soucier de la bureaucratie de la gestion de la mémoire, la réaffectation etc. Bien sûr, cela a un coût. Je pense qu'il est assez évident que dire à l'aide d'un NSArray de NSNumbers va être plus lent qu'un C Tableau de flotteurs simple itérations, donc j'ai décidé de faire quelques tests, et les résultats étaient assez choquant! Je ne l'attendais pas à ce mal. Remarque: ces tests sont effectués sur un iPhone 3G que c'est la plate-forme cible qui m'intéressait.
Dans cet essai, je fais très simple d'accès aléatoire la comparaison des performances entre une C float* et NSArray de NSNumbers
J'ai créer une boucle simple de résumer le contenu de chaque tableau et le temps eux à l'aide de mach_absolute_time(). Le NSMutableArray prend en moyenne 400 fois plus longtemps!! (pas de 400 pour cent, à seulement 400 fois plus longtemps! c'est 40,000% de plus!).
En-tête:
// Array_Speed_TestViewController.h
// Tableau De Test De Vitesse
// Créé par Mehmet Akten sur 05/02/2009.
// Le droit d'auteur MSA Visuels Ltd. 2009. Tous droits réservés.
#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
Mise en œuvre:
// Array_Speed_TestViewController.m
// Tableau De Test De Vitesse
// Créé par Mehmet Akten sur 05/02/2009.
// Le droit d'auteur MSA Visuels Ltd. 2009. Tous droits réservés.
#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 depuis l'introduction de blocs, ce qui permet d'itérer un tableau avec des blocs. Sa syntaxe n'est pas aussi beau que rapide énumération, mais il y a une fonction très intéressante: l'énumération. Si l'énumération de l'ordre n'est pas important et les emplois qui peuvent être effectuées en parallèle sans verrouillage, ce qui peut considérablement plus rapide sur un système multi-core. Plus à ce sujet dans la concurrente de l'énumération de l'article.
[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
L'idée derrière rapide énumération est d'utiliser C rapide d'accès au tableau d'optimiser l'itération. Non seulement est-il censé être plus rapide que les traditionnels NSEnumerator, mais Objective-C 2.0 fournit également une très concis de la syntaxe.
id object;
for (object in myArray) {
[self doSomethingWith:object];
}
/////////////////
NSEnumerator
C'est une forme d'itération externe: [montableau objectEnumerator] renvoie un objet. Cet objet possède une méthode nextObject que nous pouvons appeler dans une boucle jusqu'à ce qu'il retourne nil
NSEnumerator *enumerator = [myArray objectEnumerator];
id object;
while (object = [enumerator nextObject]) {
[self doSomethingWith:object];
}
/////////////////
objectAtIndex: énumération
À l'aide d'une boucle for qui augmente d'un nombre entier et l'interrogation de l'objet à l'aide de [montableau objectAtIndex:index] est la forme la plus élémentaire de l'énumération.
NSUInteger count = [myArray count];
for (NSUInteger index = 0; index < count ; index++) {
[self doSomethingWith:[myArray objectAtIndex:index]];
}
////////////// De : darkdust.net