最有效的方式来遍历NSString中的所有字符

遍历NSString中所有字符的最好方法是什么? 你想循环的string的长度,并使用该方法。

[aNSString characterAtIndex:index]; 

或者你想用户基于NSString的字符缓冲区?

我肯定会得到一个字符缓冲区,然后迭代。

 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... } 

我认为重要的是人们懂得如何处理unicode,所以我最终写了一个怪物的答案,但本着tl的精神,我会从一个应该正常工作的代码片段开始。 如果你想知道细节(你应该!),请继续阅读片段后。

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

还在我这儿? 好!

目前接受的答案似乎是混淆字节/字母的字节。 遇到unicode时,这是一个常见的问题,特别是从C背景。 Objective-C中的string表示为比字节大得多的unicode字符( unichar ),不应该与标准Cstring操作函数一起使用。

编辑 :这不是完整的故事!我很遗憾,我完全忘记了说明可组合字符,其中一个“字母”由多个unicode代码点组成,这给你一个情况,你可以有一个“字母“分解为多个unichars,每个unichars又是多个字节,胡某男孩,请参阅这个伟大的答案的细节。

问题的正确答案取决于你是否要遍历字符/字母 (与chartypes不同)或string的字节char实际意思是什么types)。 本着限制混淆的精神,我将从现在开始使用字节字母 ,避免可能有害的术语字符

如果你想做前者,并迭代string中的字母,你需要专门处理unichars(抱歉,但我们将来现在,你不能再忽略它)。 查找字母的数量很容易,这是string的长度属性。 一个例子代码片段就是这样的(和上面一样):

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

另一方面,如果你想遍历一个string中的字节,它开始变得复杂,结果将完全取决于你select使用的编码。 正确的默认select是UTF8,所以这就是我将要展示的。

这样做,你必须弄清楚得到的UTF8string将会有多less字节,这很容易出错,并使用string的-length 。 这很容易做错的一个主要原因,特别是对于美国开发者来说,一个string中包含7位ASCII字符的字符将具有相同的字节和字母长度 。 这是因为UTF8用一个字节编码7位ASCII字母,所以一个简单的testingstring和基本的英文文本可能工作得很好。

这样做的正确方法是使用方法-lengthOfBytesUsingEncoding:NSUTF8StringEncoding (或其他编码),分配一个长度的缓冲区, 然后使用-cStringUsingEncoding:将string转换为相同的编码,并将其复制到该缓冲区。 示例代码在这里:

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

为了让我们明白为什么保持直线的重要性,我将以四种不同的方式显示处理这个迭代的示例代码,两个错误和两个正确的。 这是代码:

 #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; } 

运行这段代码将输出以下内容(用NSLog cruft修剪出来),显示不同的字节和字母表示可以是(最后两个输出):

 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: ° 

都不是。 Xcode文档中的“Cocoa性能指南”中的“优化文本操作”部分推荐:

如果要迭代string的字符,则不应该使用characterAtIndex:方法分别检索每个字符。 此方法不适用于重复访问。 相反,请考虑使用getCharacters:range:方法一次性提取所有字符,并直接迭代字节。

如果要search特定字符或子string的string,请不要逐个遍历字符。 而是使用更高级别的方法,例如rangeOfString:rangeOfCharacterFromSet:substringWithRange: rangeOfCharacterFromSet:这些方法为searchNSString字符进行了优化。

有关如何让rangeOfCharacterFromSet:遍历string的字符而不是自己执行的示例,请参阅如何从NSString右端删除空白的Stack Overflow答案 。

虽然丹尼尔的解决scheme可能大部分时间工作,但我认为解决scheme取决于上下文。 例如,我有一个拼写应用程序,需要遍历每个字符,因为它出现在屏幕上,可能不符合它在内存中表示的方式。 对于用户提供的文本尤其如此。

在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: @", "]); } 

给它一个像马尼亚娜这样的词可能会产生:

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

但是它可以很容易地产生:

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

如果string是以unicodeforms预先生成的,则会生成前一个string,如果string是以unicodeforms进行分解,则会生成该string。

您可能会认为这可以通过使用NSString的预组合StringStringWithCanonicalMapping或预组合StringStringWithCompatibilityMapping的结果来避免,但事实并非如Apple在技术问答1225中所警告的那样。 例如,像e̊gâds (我完全组成)这样的string,即使在转换为预分解forms之后仍然会产生以下内容。

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

我的解决scheme是使用NSString的enumerateSubstringsInRange传递NSStringEnumerationByComposedCharacterSequences作为枚举选项。 重写前面的例子看起来像这样:

 - (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: @", "]); } 

如果我们喂这个版本,那么我们得到

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

如预期的那样,这是我想要的。

字符和字形集群的文档部分也可能有助于解释其中的一些。

注意:看起来像我使用的一些Unicodestring在格式化为代码时跳出来。 我使用的string是mañana和e'gâds。

虽然你在技术上会获得单独的NSString值,但这里有一个替代方法:

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

__unused int我是必要的沉默编译器警告。)

尝试枚举带块的string

创buildNSString的类别

。H

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

.M

 @implementation NSString (Category) - (void)enumerateCharactersUsingBlock:(void (^)(NSString *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 

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

你不应该使用

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

你应该使用内存分配

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

并在最终使用

 free(buffer); 

以避免记忆问题。