迭代NSString中所有字符的最有效方法

aah*_*ens 50 objective-c

迭代NSString中所有字符的最佳方法是什么?是否要循环遍历字符串的长度并使用该方法.

[aNSString characterAtIndex:index];
Run Code Online (Sandbox Code Playgroud)

或者你想根据NSString使用char缓冲区?

Dan*_*uce 138

我认为人们了解如何处理unicode非常重要,所以我最终写了一个怪物答案,但本着tl; dr的精神,我将从一个应该正常工作的片段开始.如果您想了解详细信息(您应该知道!),请在摘录后继续阅读.

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]);
}
Run Code Online (Sandbox Code Playgroud)

还在我这儿?好!

当前接受的答案似乎是用字符/字母混淆字节.遇到unicode时这是一个常见问题,尤其是来自C背景.Objective-C中的字符串表示为unicode字符(unichar),它们比字节大得多,不应与标准C字符串操作函数一起使用.

(编辑:这不是完整的故事!令我非常遗憾的是,我完全忘了考虑可组合字符,其中"字母"由多个unicode代码点组成.这给你一个你可以拥有一个"字母"解析为多个unichars,而每个unichars又是多个字节.Hoo boy.请参阅这个很棒的答案."

问题的正确答案取决于您是要迭代字符/字母(与类型不同char)还是字符串的字节(类型char实际意味着什么).本着限制混淆的精神,我将从现在开始使用术语字节字母,避免可能存在歧义的术语字符.

如果你想做前者并迭代字符串中的字母,你需要专门处理unichars(对不起,但我们现在将来,你不能再忽略它了).查找字母数量很容易,这是字符串的长度属性.一个示例代码段就是这样(与上面相同):

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]);
}
Run Code Online (Sandbox Code Playgroud)

另一方面,如果要迭代字符串中的字节,它会开始变得复杂,结果将完全取决于您选择使用的编码.合适的默认选择是UTF8,这就是我要展示的内容.

这样做你必须弄清楚生成的UTF8字符串将包含多少字节,这是一个容易出错并使用字符串的步骤-length.这很容易出错的一个主要原因,特别是对于美国开发人员而言,字母落入7位ASCII频谱的字符串将具有相等的字节和字母长度.这是因为UTF8使用单个字节编码7位ASCII字母,因此简单的测试字符串和基本的英文文本可能完全正常.

执行此操作的正确方法是使用方法-lengthOfBytesUsingEncoding:NSUTF8StringEncoding(或其他编码),分配具有该长度的缓冲区,然后将字符串转换为相同的编码-cStringUsingEncoding:并将其复制到该缓冲区中.这里的示例代码:

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]);
}
Run Code Online (Sandbox Code Playgroud)

为了说明为什么保持正确的重要性,我将展示以四种不同方式处理此迭代的示例代码,两个错误,两个正确.这是代码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

运行此代码将输出以下内容(修剪出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: °
Run Code Online (Sandbox Code Playgroud)

  • 为什么这不是今年SO的最佳答案?为什么没有更多的赞成?为什么没有人建造雕像来纪念丹尼尔?为什么世界上有这么多不公正?! (17认同)
  • 为null终止符留出空间.:) (3认同)
  • `[str getCharacters:buffer range:NSMakeRange(0, len)];` 不添加空终止符。所以你不需要+1。 (2认同)

Cas*_*ser 28

虽然Daniel的解决方案可能在大多数时间都有效,但我认为解决方案取决于上下文.例如,我有一个拼写应用程序,需要迭代屏幕上显示的每个字符,这可能与它在内存中的表示方式不对应.对于用户提供的文本尤其如此.

在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: @", "]);
}
Run Code Online (Sandbox Code Playgroud)

像喂mañana这样的话可能产生:

mañana = m, a, ñ, a, n, a
Run Code Online (Sandbox Code Playgroud)

但它可以很容易地产生:

mañana = m, a, n, ?, a, n, a
Run Code Online (Sandbox Code Playgroud)

如果字符串是预组合的unicode形式,则生成前者,如果是分解形式,则生成前者.

您可能认为可以通过使用NSString的precomposedStringWithCanonicalMapping或precomposedStringWithCompatibilityMapping的结果来避免这种情况,但不一定是苹果在技术问答1225中警告的情况.例如e?gâds,即使在转换为预合成形式之后,像我(我完全组成)的字符串仍然会产生以下内容.

 e?gâds = e, ?, g, â, d, s
Run Code Online (Sandbox Code Playgroud)

我的解决方案是使用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: @", "]);
}
Run Code Online (Sandbox Code Playgroud)

如果我们提供这个版本,e?gâds那么我们得到

e?gâds = e?, g, â, d, s
Run Code Online (Sandbox Code Playgroud)

正如所料,这就是我想要的.

关于字符和字母集群的文档部分也可能有助于解释其中的一些内容.

注意:看起来我使用的一些unicode字符串在格式化为代码时会跳闸.我使用的字符串是mañana和e̊gâds.

  • 亲爱的,我完全忘记了可组合字符。我在我的笔记中添加了注释和参考。我想这里的教训是:文字很难,老兄。 (2认同)

ma1*_*w28 25

都不是.Xcode文档中"Cocoa性能指南""优化文本操作"部分建议:

如果要迭代字符串的字符,则不应该执行的操作之一是使用该 characterAtIndex:方法分别检索每个字符.此方法不适用于重复访问.相反,请考虑使用该getCharacters:range:方法一次性获取字符 并直接迭代字节.

如果要在字符串中搜索特定字符或子字符串,请不要逐个遍历字符.取而代之的是,使用较高水平的方法,例如rangeOfString:, rangeOfCharacterFromSet:,或 substringWithRange:,其被用于搜索优化的NSString 字符.

请参阅此Stack Overflow答案,了解如何从右端删除空格,以NSString获取如何rangeOfCharacterFromSet:迭代字符串字符而不是自己执行此操作的示例.


Jac*_*kin 18

我肯定会首先得到一个char缓冲区,然后迭代它.

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...
}
Run Code Online (Sandbox Code Playgroud)

  • 这是一种可行的方法,但值得注意的是,沿着这些行的NSString的任何字符转换都会遇到一些非常棘手的多字节文本边缘情况,并且最好尽可能避免.(遗憾的是,仅使用UTF-16或UTF-32不足以解决国际文本的所有问题,尽管它会将你的记忆要求发送到月球.) (15认同)
  • Chuck,还有什么选择.你是说使用characterAtIndex尽管费用高吗? (2认同)