54 votes

Le moyen le plus efficace d'itérer sur tous les caractères d'une chaîne NSString

Quelle est la meilleure façon de parcourir tous les caractères dans une chaîne NSString? Souhaitez-vous passer en boucle sur la longueur de la chaîne et utiliser la méthode.

 [aNSString characterAtIndex:index];
 

ou voudriez-vous utiliser un tampon de caractères basé sur NSString?

148voto

Daniel Bruce Points 3413

Je pense qu'il est important que les gens comprennent la façon de traiter avec unicode, donc j'ai fini par écrire un monstre de réponse, mais dans l'esprit de tl;dr je vais commencer avec un morceau de code qui devrait fonctionner correctement. Si vous voulez connaître les détails (que vous devriez!), s'il vous plaît continuer la lecture après le fragment de code.

NSUInteger len = [str length];
unichar buffer[len+1];

[str getCharacters:buffer range:NSMakeRange(0, len)];

NSLog(@"getCharacters:range: with unichar buffer");
for(int i = 0; i < len; i++) {
  NSLog(@"%C", buffer[i]);
}

Toujours avec moi? Bon!

L'actuel accepté la réponse semble être source de confusion octets avec des personnages et des lettres. C'est un problème courant lors de la rencontre d'unicode, en particulier à partir d'un C en arrière-plan. Les chaînes en Objective-C sont représentés comme des caractères unicode (unichar) qui sont beaucoup plus gros que les octets et ne doit pas être utilisé avec la norme C fonctions de manipulation de chaîne.

La bonne réponse à la question dépend si vous souhaitez effectuer une itération sur les caractères et les lettres (comme distinct du type char) ou les octets de la chaîne (de quel type char signifie en fait). Dans l'esprit de la limitation de la confusion, je vais utiliser les termes d'octets et de la lettre à partir de maintenant, en évitant la peut-être ambiguë en terme de caractère.

Si vous voulez faire de l'ancien et itérer sur les lettres dans la chaîne, vous devez exclusivement avec unichars (désolé, mais nous sommes dans le futur maintenant, vous ne pouvez pas l'ignorer plus). Trouver la quantité de lettres, c'est facile, c'est la longueur de la chaîne de propriété. Un exemple extrait est en tant que tel (le même que ci-dessus):

NSUInteger len = [str length];
unichar buffer[len+1];

[str getCharacters:buffer range:NSMakeRange(0, len)];

NSLog(@"getCharacters:range: with unichar buffer");
for(int i = 0; i < len; i++) {
  NSLog(@"%C", buffer[i]);
}

Si, d'autre part, vous souhaitez effectuer une itération sur les octets en une chaîne de caractères, ça commence à devenir compliqué et le résultat dépend de l'encodage vous choisissez d'utiliser. L'décent choix par défaut est UTF-8, donc c'est ce que je vais montrer.

Ce faisant, vous devez déterminer combien d'octets sont la résultante chaîne UTF8 sera, une étape où il est facile de se tromper et d'utiliser la chaîne de caractères -length. L'une des principales raisons de cette très facile à faire mal, surtout pour un développeur, c'est qu'une chaîne de caractères avec des lettres de tomber dans l'ASCII 7 bits spectre sera égal octet et lettre de longueurs. C'est parce que UTF8 code ASCII 7 bits lettres avec un seul octet, donc une simple chaîne de test et un anglais de base, le texte pourrait fonctionner parfaitement bien.

La bonne façon de le faire est d'utiliser la méthode de -lengthOfBytesUsingEncoding:NSUTF8StringEncoding (ou autre encodage), allouer un tampon avec cette longueur, puis convertir la chaîne de la même encodage avec -cStringUsingEncoding: et le copier dans la mémoire tampon. Exemple de code ici:

NSUInteger byteLength = [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
char proper_c_buffer[byteLength+1];
strncpy(proper_c_buffer, [str cStringUsingEncoding:NSUTF8StringEncoding], byteLength);

NSLog(@"strncpy with proper length");
for(int i = 0; i < byteLength; i++) {
  NSLog(@"%c", proper_c_buffer[i]);
}

Juste pour enfoncer le clou quant à pourquoi il est important de garder les choses claires, je vais montrer un exemple de code qui gère cette itération de quatre façons différentes, deux de mal et les deux corrects. C'est le code:

#import <Foundation/Foundation.h>

int main() {
  NSString *str = @"буква";
  NSUInteger len = [str length];

  // Try to store unicode letters in a char array. This will fail horribly
  // because getCharacters:range: takes a unichar array and will probably
  // overflow or do other terrible things. (the compiler will warn you here,
  // but warnings get ignored)
  char c_buffer[len+1];
  [str getCharacters:c_buffer range:NSMakeRange(0, len)];

  NSLog(@"getCharacters:range: with char buffer");
  for(int i = 0; i < len; i++) {
    NSLog(@"Byte %d: %c", i, c_buffer[i]);
  }

  // Copy the UTF string into a char array, but use the amount of letters
  // as the buffer size, which will truncate many non-ASCII strings.
  strncpy(c_buffer, [str UTF8String], len);

  NSLog(@"strncpy with UTF8String");
  for(int i = 0; i < len; i++) {
    NSLog(@"Byte %d: %c", i, c_buffer[i]);
  }

  // Do It Right (tm) for accessing letters by making a unichar buffer with
  // the proper letter length
  unichar buffer[len+1];
  [str getCharacters:buffer range:NSMakeRange(0, len)];

  NSLog(@"getCharacters:range: with unichar buffer");
  for(int i = 0; i < len; i++) {
    NSLog(@"Letter %d: %C", i, buffer[i]);
  }

  // Do It Right (tm) for accessing bytes, by using the proper
  // encoding-handling methods
  NSUInteger byteLength = [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
  char proper_c_buffer[byteLength+1];
  const char *utf8_buffer = [str cStringUsingEncoding:NSUTF8StringEncoding];
  // We copy here because the documentation tells us the string can disappear
  // under us and we should copy it. Just to be safe
  strncpy(proper_c_buffer, utf8_buffer, byteLength);

  NSLog(@"strncpy with proper length");
  for(int i = 0; i < byteLength; i++) {
    NSLog(@"Byte %d: %c", i, proper_c_buffer[i]);
  }
  return 0;
}

L'exécution de ce code de sortie de celui-ci (avec NSLog trucs garni out), montrant exactement COMMENT les différents octets et lettre de représentations (les deux dernières sorties):

getCharacters:range: with char buffer
Byte 0: 1
Byte 1: 
Byte 2: C
Byte 3: 
Byte 4: :
strncpy with UTF8String
Byte 0: Ð
Byte 1: ±
Byte 2: Ñ
Byte 3: 
Byte 4: Ð
getCharacters:range: with unichar buffer
Letter 0: б
Letter 1: у
Letter 2: к
Letter 3: в
Letter 4: а
strncpy with proper length
Byte 0: Ð
Byte 1: ±
Byte 2: Ñ
Byte 3: 
Byte 4: Ð
Byte 5: º
Byte 6: Ð
Byte 7: ²
Byte 8: Ð
Byte 9: °

29voto

somegeekintn Points 1536

Tandis que Daniel la solution sera sans doute la plupart du temps, je pense que la solution est dépendante du contexte. Par exemple, j'ai une orthographe application et à itérer sur chaque personnage tel qu'il apparaît à l'écran ce qui peut ne pas correspondre à la manière dont il est représenté dans la mémoire. Cela est particulièrement vrai pour le texte fourni par l'utilisateur.

En utilisant quelque chose comme cette catégorie sur NSString:

- (void) dumpChars
{
    NSMutableArray  *chars = [NSMutableArray array];
    NSUInteger      len = [self length];
    unichar         buffer[len+1];

    [self getCharacters: buffer range: NSMakeRange(0, len)];
    for (int i=0; i<len; i++) {
        [chars addObject: [NSString stringWithFormat: @"%C", buffer[i]]];
    }

    NSLog(@"%@ = %@", self, [chars componentsJoinedByString: @", "]);
}

Et de la nourrir un mot comme mañana peut produire:

mañana = m, a, ñ, a, n, a

Mais il pourrait tout aussi facilement produire:

mañana = m, a, n, ̃, a, n, a

Le premier sera produite si la chaîne est en précomposé forme unicode et, plus tard, si il est en forme décomposée.

Vous pourriez penser que cela pourrait être évité en utilisant le résultat de NSString de precomposedStringWithCanonicalMapping ou precomposedStringWithCompatibilitymapping, mais ce n'est pas nécessairement le cas, comme Apple avertit en Technique Q&A 1225. Par exemple une chaîne de caractères comme e̊gâds (dont j'ai totalement composé) produit toujours de la suite, même après la conversion à un précomposé forme.

 e̊gâds = e, ̊, g, â, d, s

La solution pour moi est d'utiliser NSString de enumerateSubstringsInRange passant NSStringEnumerationByComposedCharactersequences que l'énumération de l'option. La réécriture de l'exemple précédent pour ressembler à ceci:

- (void) dumpSequences
{
    NSMutableArray  *chars = [NSMutableArray array];

    [self enumerateSubstringsInRange: NSMakeRange(0, [self length]) options: NSStringEnumerationByComposedCharacterSequences
        usingBlock: ^(NSString *inSubstring, NSRange inSubstringRange, NSRange inEnclosingRange, BOOL *outStop) {
        [chars addObject: inSubstring];
    }];

    NSLog(@"%@ = %@", self, [chars componentsJoinedByString: @", "]);
}

Si nous nourrir de cette version e̊gâds alors nous obtenons

e̊gâds = e̊, g, â, d, s

comme prévu, ce qui est ce que je veux.

La section de la documentation sur les Personnages et Graphème Clusters peut également être utile dans l'explication de certains de ces.

Note: il Semble bien que certains des chaînes unicode j'ai utilisé du trip jusqu'ALORS, quand formaté en tant que code. Les cordes que j'ai utilisées sont mañana, et egâds.

25voto

MattDiPasquale Points 23842

Ni. Le "Optimiser Votre Texte Manipulations" de la section "Cacao lignes Directrices sur le Rendement" dans Xcode Documentation recommande:

Si vous souhaitez effectuer une itération sur les caractères d'une chaîne, l'un des les choses vous ne devriez pas faire est d'utiliser la characterAtIndex: méthode pour récupérer chaque personnage séparément. Cette méthode n'est pas conçu pour des accès répétés. Au lieu de cela, envisager de récupérer l' des personnages tout à la fois à l'aide de la getCharacters:range: méthode et itération sur les octets directement.

Si vous souhaitez rechercher une chaîne de caractères pour caractères spécifiques ou des sous-chaînes, ne pas de parcourir les caractères un par un. Au lieu de cela, l'utilisation de plus haut niveau des méthodes telles que rangeOfString:, rangeOfCharacterFromSet:, ou substringWithRange:, qui sont optimisé pour la recherche de la NSString des personnages.

Voir ce Débordement de Pile réponse sur la Façon de supprimer l'espace à partir de l'extrémité droite de la NSString pour un exemple de comment laissez - rangeOfCharacterFromSet: itérer sur les caractères de la chaîne au lieu de le faire vous-même.

19voto

Jacob Relkin Points 90729

Je voudrais certainement obtenir un tampon de chars d'abord, puis itérer sur cela.

 NSString *someString = ...

unsigned int len = [someString length];
char buffer[len];

//This way:
strncpy(buffer, [someString UTF8String]);

//Or this way (preferred):

[someString getCharacters:buffer range:NSMakeRange(0, len)];

for(int i = 0; i < len; ++i) {
   char current = buffer[i];
   //do something with current...
}
 

2voto

Scott Gardner Points 692

Bien que vous obteniez techniquement des valeurs individuelles de NSString, voici une approche alternative:

 NSRange range = NSMakeRange(0, 1);
for (__unused int i = range.location; range.location < [starring length]; range.location++) {
  NSLog(@"%@", [aNSString substringWithRange:range]);
}
 

(Le bit __ i non utilisé est nécessaire pour désactiver l'avertissement du compilateur.)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X