Le moyen le plus efficace d’itérer tous les caractères d’un NSSsortingng

Quelle est la meilleure façon de parcourir tous les caractères d’un NSSsortingng? Voulez-vous faire une boucle sur la longueur de la chaîne et utiliser la méthode.

[aNSSsortingng characterAtIndex:index]; 

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

Je commencerais certainement par obtenir un tampon de caractères, puis je le répéterais.

 NSSsortingng *someSsortingng = ... unsigned int len = [someSsortingng length]; char buffer[len]; //This way: strncpy(buffer, [someSsortingng UTF8Ssortingng]); //Or this way (preferred): [someSsortingng getCharacters:buffer range:NSMakeRange(0, len)]; for(int i = 0; i < len; ++i) { char current = buffer[i]; //do something with current... } 

Je pense qu’il est important que les gens comprennent comment traiter unicode, alors j’ai fini par écrire une réponse monstre, mais dans l’esprit de tl; dr, je commencerai par un extrait qui devrait fonctionner correctement. Si vous voulez connaître les détails (ce que vous devriez faire!), Continuez à lire après l’extrait.

 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]); } 

Encore avec moi? Bien!

Les réponses acceptées actuelles semblent confondre les octets avec les caractères / lettres. Il s’agit d’un problème courant lorsqu’on rencontre unicode, en particulier en arrière-plan C. Les chaînes en Objective-C sont représentées par des caractères Unicode ( unichar ) qui sont beaucoup plus grands que les octets et ne doivent pas être utilisés avec les fonctions de manipulation de chaînes C standard.

( Edit : Ceci n'est pas l'histoire complète! À ma grande honte, j'avais complètement oublié de prendre en compte les caractères composables, où une "lettre" est composée de plusieurs points de code Unicode. Cela vous donne une situation où vous pouvez en avoir un " lettre "résolvant en unichars multiples, qui sont à leur tour plusieurs octets. Hoo boy. S'il vous plaît se référer à cette excellente réponse pour les détails à ce sujet.)

La réponse correcte à la question dépend de si vous voulez effectuer une itération sur les caractères / lettres (à la différence du type char ) ou sur les octets de la chaîne (ce que signifie réellement le type char ). Dans un esprit de limitation de la confusion, j'utiliserai désormais les termes octet et lettre , en évitant le terme de caractère éventuellement ambiant.

Si vous voulez faire le premier et parcourir les lettres de la chaîne, vous devez traiter exclusivement avec unichars (désolé, mais nous ne sums plus dans le futur, vous ne pouvez plus l'ignorer). Trouver la quantité de lettres est facile, c'est la propriété de longueur de la chaîne. Un exemple d’extrait de code est le même (comme 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, par contre, vous voulez parcourir les octets d'une chaîne, cela commence à se compliquer et le résultat dépend entièrement de l'encodage que vous choisissez d'utiliser. Le choix par défaut décent est UTF8, alors c'est ce que je vais montrer.

Pour ce faire, vous devez déterminer le nombre d'octets de la chaîne UTF8 obtenue, une étape où il est facile de se tromper et d'utiliser la -length la chaîne. L'une des principales raisons de ce problème très facile, en particulier pour un développeur américain, est qu'une chaîne dont les lettres se trouvent dans le spectre ASCII à 7 bits aura des longueurs d'octet et de lettre égales . En effet, UTF8 code les lettres ASCII 7 bits avec un seul octet, de sorte qu'une chaîne de test simple et un texte anglais de base peuvent parfaitement fonctionner.

La méthode appropriée consiste à utiliser la méthode -lengthOfBytesUsingEncoding:NSUTF8SsortingngEncoding (ou un autre codage), à ​​allouer un tampon de cette longueur, puis à convertir la chaîne avec le même encodage avec -cSsortingngUsingEncoding: et à le copier dans ce tampon. Exemple de code ici:

 NSUInteger byteLength = [str lengthOfBytesUsingEncoding:NSUTF8SsortingngEncoding]; char proper_c_buffer[byteLength+1]; strncpy(proper_c_buffer, [str cSsortingngUsingEncoding:NSUTF8SsortingngEncoding], byteLength); NSLog(@"strncpy with proper length"); for(int i = 0; i < byteLength; i++) { NSLog(@"%c", proper_c_buffer[i]); } 

Juste pour faire comprendre pourquoi il est important de garder les choses claires, je montrerai un exemple de code qui gère cette itération de quatre manières différentes, deux fausses et deux correctes. Ceci est le code:

 #import  int main() { NSSsortingng *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 comstackr 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 affichera les informations suivantes (avec NSLog tronqué), indiquant exactement la différence entre les représentations d'octets et de lettres (les deux dernières sorties):

 getCharacters:range: with char buffer Byte 0: 1 Byte 1: Byte 2: C Byte 3: Byte 4: : strncpy with UTF8Ssortingng 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: ° 

Ni. La section «Optimiser vos manipulations de texte» des «Directives de performance Cocoa» de la documentation Xcode recommande:

Si vous voulez parcourir les caractères d’une chaîne, l’une des choses à ne pas faire est d’utiliser la méthode characterAtIndex: pour récupérer chaque caractère séparément. Cette méthode n’est pas conçue pour un access répété. Au lieu de cela, envisagez de récupérer les caractères à la fois en utilisant la méthode getCharacters:range: et en itérant directement sur les octets.

Si vous souhaitez rechercher une chaîne pour des caractères ou des sous-chaînes spécifiques, ne parcourez pas les caractères un par un. Utilisez plutôt des méthodes de niveau supérieur telles que rangeOfSsortingng: rangeOfCharacterFromSet: ou subssortingngWithRange: rangeOfCharacterFromSet: optimisées pour la recherche des caractères NSSsortingng .

Voir cette réponse Stack Overflow sur Comment supprimer les espaces à l’extrémité droite de NSSsortingng pour un exemple de la façon de laisser rangeOfCharacterFromSet: itérer sur les caractères de la chaîne au lieu de le faire vous-même.

Bien que la solution de Daniel fonctionnera probablement la plupart du temps, je pense que la solution dépend du contexte. Par exemple, j’ai une application d’orthographe et je dois parcourir chaque caractère tel qu’il apparaît à l’écran, ce qui peut ne pas correspondre à la manière dont il est représenté en mémoire. Cela est particulièrement vrai pour le texte fourni par l’utilisateur.

Utiliser quelque chose comme cette catégorie sur NSSsortingng:

 - (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 

Et nourrir un mot comme mañana pourrait produire:

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

Mais il pourrait tout aussi bien produire:

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

Le premier sera produit si la chaîne est en forme unicode précomposée et le plus tard si elle est décomposée.

Vous pourriez penser que cela pourrait être évité en utilisant le résultat de precomposedSsortingngWithCanonicalMapping ou precomposedSsortingngWithCompatibilityMapping de NSSsortingng, mais ce n'est pas forcément le cas lorsque Apple avertit dans Technical Q & A 1225 . Par exemple, une chaîne telle que e̊gâds (que j'ai totalement créée) produit toujours ce qui suit même après la conversion en une forme précomposée.

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

La solution pour moi consiste à utiliser enumerateSubssortingngsInRange de NSSsortingng en transmettant NSSsortingngEnumerationByComposedCharacterSequences comme option d'énumération. Réécrire l'exemple précédent pour ressembler à ceci:

 - (void) dumpSequences { NSMutableArray *chars = [NSMutableArray array]; [self enumerateSubssortingngsInRange: NSMakeRange(0, [self length]) options: NSSsortingngEnumerationByComposedCharacterSequences usingBlock: ^(NSSsortingng *inSubssortingng, NSRange inSubssortingngRange, NSRange inEnclosingRange, BOOL *outStop) { [chars addObject: inSubssortingng]; }]; NSLog(@"%@ = %@", self, [chars componentsJoinedBySsortingng: @", "]); } 

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

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

comme prévu, c'est ce que je veux.

La section de la documentation sur les caractères et les grappes de graphèmes peut également être utile pour en expliquer une partie.

Note: On dirait que certaines des chaînes Unicode que j'ai utilisées déclenchent SO lorsqu'elles sont formatées en code. Les cordes que j'ai utilisées sont mañana et e̊gâds.

Bien que vous obtiendrez techniquement des valeurs NSSsortingng individuelles, 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 __unused int i est nécessaire pour faire taire l'avertissement du compilateur.)

essayer enum ssortingng avec des blocs

Créer une catégorie de NSSsortingng

.h

 @interface NSSsortingng (Category) - (void)enumerateCharactersUsingBlock:(void (^)(NSSsortingng *character, NSInteger idx, bool *stop))block; @end 

.m

 @implementation NSSsortingng (Category) - (void)enumerateCharactersUsingBlock:(void (^)(NSSsortingng *character, NSInteger idx, bool *stop))block { bool _stop = NO; for(NSInteger i = 0; i < [self length] && !_stop; i++) { NSString *character = [self substringWithRange:NSMakeRange(i, 1)]; block(character, i, &_stop); } } @end 

Exemple

 NSSsortingng *ssortingng = @"Hello World"; [ssortingng enumerateCharactersUsingBlock:^(NSSsortingng *character, NSInteger idx, bool *stop) { NSLog(@"char %@, i: %li",character, (long)idx); }]; 

Vous ne devriez pas utiliser

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

vous devez utiliser l’allocation de mémoire

 NSUInteger len = [str length]; unichar* buffer = (unichar*) malloc (len+1)*sizeof(unichar); 

et à la fin l’utilisation

 free(buffer); 

afin d’éviter des problèmes de mémoire.