什么是NSString的UTF8String的CFString Equiv?

Cli*_*iff 5 iphone objective-c

我今天仍然坚持使用stoopid因为我无法将一段简单的ObjC代码转换为它的Cpp等价物.我有这个:

  const UInt8 *myBuffer = [(NSString*)aRequest UTF8String];
Run Code Online (Sandbox Code Playgroud)

而我正试图用这个替换它:

  const UInt8 *myBuffer = (const UInt8 *)CFStringGetCStringPtr(aRequest, kCFStringEncodingUTF8);
Run Code Online (Sandbox Code Playgroud)

这是一个严密的单元测试,它通过CFNetwork API在套接字上编写示例HTTP请求.我有工作的ObjC代码,我正在尝试移植到C++.我正在逐步用他们的免费桥接等价替换NS API调用.到目前为止,一切都是一对一的,直到最后一行.这就像需要完成的最后一块.

joh*_*hne 14

这是Cocoa在幕后制作所有杂乱内容的事情之一,你永远不会真正意识到事情是多么复杂,直到你不得不卷起袖子自己做.

简单回答为什么它不是"简单"是因为NSString(和CFString)处理处理多个字符集,Unicode等等的所有复杂细节,同时提供一个简单,统一的API来操作字符串.它的面向对象是最好的 - "如何" (NS|CF)String处理具有不同字符串编码的字符串(UTF8,MacRoman,UTF16,ISO 2022日语等)的细节是私有实现细节.这一切都"有效".

它有助于理解如何[@"..." UTF8String]工作.这是一个私有的实现细节,所以这不是福音,而是基于观察到的行为.当你给一个字符串发一条UTF8String消息时,字符串会做一些近似的事情(实际上没有经过测试,所以请考虑伪代码,实际上有更简单的方法来做同样的事情,所以这个过于冗长):

- (const char *)UTF8String
{
  NSUInteger utf8Length = [self lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
  NSMutableData *utf8Data = [NSMutableData dataWithLength:utf8Length + 1UL];
  char *utf8Bytes = [utf8Data mutableBytes];
  [self     getBytes:utf8Bytes
           maxLength:utf8Length
          usedLength:NULL
            encoding:NSUTF8StringEncoding
             options:0UL
               range:NSMakeRange(0UL, [self length])
      remainingRange:NULL];
  return(utf8Bytes);
}
Run Code Online (Sandbox Code Playgroud)

您不必担心处理-UTF8String返回的缓冲区的内存管理问题,因为它NSMutableData是自动释放的.

字符串对象可以自由地以任何形式保存字符串的内容,因此无法保证其内部表示形式最符合您的需求(在本例中为UTF8).如果你只使用普通的C,你将不得不处理管理一些内存来保存可能需要的任何字符串转换.曾经简单的-UTF8String方法调用现在变得更加复杂.

大多数NSString实际上是在CoreFoundation /中实现的CFString,所以显然有一个来自CFStringRef- > 的路径-UTF8String.这只是不是整洁和简单NSString-UTF8String.大多数并发症都是内存管理.以下是我过去的解决方法:

void someFunction(void) {
  CFStringRef cfString; // Assumes 'cfString' points to a (NS|CF)String.

  const char *useUTF8StringPtr = NULL;
  UInt8 *freeUTF8StringPtr = NULL;

  CFIndex stringLength = CFStringGetLength(cfString), usedBytes = 0L;

  if((useUTF8StringPtr = CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8)) == NULL) {
    if((freeUTF8StringPtr = malloc(stringLength + 1L)) != NULL) {
      CFStringGetBytes(cfString, CFRangeMake(0L, stringLength), kCFStringEncodingUTF8, '?', false, freeUTF8StringPtr, stringLength, &usedBytes);
      freeUTF8StringPtr[usedBytes] = 0;
      useUTF8StringPtr = (const char *)freeUTF8StringPtr;
    }
  }

  long utf8Length = (long)((freeUTF8StringPtr != NULL) ? usedBytes : stringLength);

  if(useUTF8StringPtr != NULL) {
    // useUTF8StringPtr points to a NULL terminated UTF8 encoded string.
    // utf8Length contains the length of the UTF8 string.

    // ... do something with useUTF8StringPtr ...
  }

  if(freeUTF8StringPtr != NULL) { free(freeUTF8StringPtr); freeUTF8StringPtr = NULL; }
}
Run Code Online (Sandbox Code Playgroud)

注意:我没有测试过这段代码,但它是从工作代码中修改过来的.因此,除了明显的错误,我认为它应该工作.

上面尝试获取指向CFString用于存储字符串内容的缓冲区的指针.如果CFString碰巧有以UTF8编码的字符串内容(或适当兼容的编码,如ASCII),那么它很可能CFStringGetCStringPtr()会返回非NULL.这显然是最好,最快的案例.如果由于某种原因它无法获得该指针,比如说CFString它的内容是用UTF16编码的,那么它会分配一个缓冲区,malloc()该缓冲区足够大,当它被转码为UTF8时包含整个字符串.然后,在函数结束时,它会检查是否已分配内存并free()在必要时进行分配.

现在有一些提示和技巧...... CFString'倾向于'(这是一个私有的实现细节,所以它可以并且确实在版本之间发生变化)保持'简单'字符串编码为MacRoman,这是一个8位宽的编码.与UTF8一样,MacRoman是ASCII的超集,因此所有字符<128都等同于它们的ASCII对应物(换句话说,任何字符<128都是ASCII).在MacRoman中,字符> = 128是'特殊'字符.它们都具有Unicode等价物,并且往往是额外的货币符号和"扩展的西方"字符.有关更多信息,请参阅Wikipedia - MacRoman.但仅仅因为CFString它说它是MacRoman(CFString编码值kCFStringEncodingMacRoman,NSString编码值NSMacOSRomanStringEncoding)并不意味着它的字符> = 128.如果kCFStringEncodingMacRoman返回的编码字符串CFStringGetCStringPtr()完全由字符<128组成,那么它完全等同于其ASCII(kCFStringEncodingASCII)编码表示,这也完全等同于字符串UTF8(kCFStringEncodingUTF8)编码表示.

根据您的要求,您可以使用kCFStringEncodingMacRoman而不是kCFStringEncodingUTF8在打电话时"过来" CFStringGetCStringPtr().如果你的字符串需要严格的UTF8编码但是使用kCFStringEncodingMacRoman,那么"可能"(可能)会更快,然后检查以确保返回的字符串CFStringGetCStringPtr(string, kCFStringEncodingMacRoman)仅包含<128的字符.如果字符串中有> = 128的字符,那么通过malloc()缓冲区来缓慢路由以保存转换后的结果.例:

CFIndex stringLength = CFStringGetLength(cfString), usedBytes = 0L;

useUTF8StringPtr = CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8);

for(CFIndex idx = 0L; (useUTF8String != NULL) && (useUTF8String[idx] != 0); idx++) {
  if(useUTF8String[idx] >= 128) { useUTF8String = NULL; }
}

if((useUTF8String == NULL) && ((freeUTF8StringPtr = malloc(stringLength + 1L)) != NULL)) {
  CFStringGetBytes(cfString, CFRangeMake(0L, stringLength), kCFStringEncodingUTF8, '?', false, freeUTF8StringPtr, stringLength, &usedBytes);
  freeUTF8StringPtr[usedBytes] = 0;
  useUTF8StringPtr = (const char *)freeUTF8StringPtr;
}
Run Code Online (Sandbox Code Playgroud)

就像我说的那样,你并不真正欣赏Cocoa为你做多少工作,直到你必须自己完成.:)


小智 5

在上面的示例代码中,出现以下内容:

CFIndex stringLength = CFStringGetLength(cfString)
Run Code Online (Sandbox Code Playgroud)

然后,stringLength被用于malloc()一个多字节的临时缓冲区,加1.

但是头文件CFStringGetLength()明确表示它返回的是16位Unicode字符的数量,而不是字节数.因此,如果其中一些Unicode字符超出ASCII范围,则malloc()缓冲区的长度不足以保持字符串的UTF-8转换.

也许我错过了一些东西,但为了绝对安全,当它们全部转换为UTF-8时,保存N个任意Unicode字符所需的字节数最多为4*n.