NSString initWithData 返回 null

25

我正在使用 NSURLConnection 从一个网站拉取数据,并将接收到的数据存储在 NSMutableData 实例中。在 connectionDidFinishLoading 委托方法中,通过调用 NSString 的相应方法,将数据转换为字符串:

NSString *result = [[NSString alloc] initWithData:data 
                                     encoding:NSUTF8StringEncoding]

结果字符串为空。然而,如果我使用 NSASCIIStringEncoding,我可以得到适当的字符串,尽管像预期的那样将Unicode字符弄乱了。服务器的 Content-Type 头未指定UTF-8编码,但我已经尝试过许多类似情况的网站,那里的字符串转换都很好。看起来问题只涉及给定的 Web 服务,但我不知道原因。

另外一方面,从API中提取网页和数据是否是良好的实践,例如缓冲数据、将其转换为字符串,然后在字符串之后操作?

非常感谢!


2
调试时,如果方法失败,应将数据保存到临时目录中的文件中,以便您可以在TextWrangler或其他工具中打开该文件,查看其实际编码。 - Peter Hosey
5个回答

27
你说它“肯定是UTF-8”,但是如果没有Content-Type头,你就不知道确切的编码方式。(即使有说明编码方式的头,也可能是错误的。)
我的猜测是,你的数据通常是ASCII编码,它总是可以正确解析为UTF-8编码,但是有时你试图解析实际上是ISO 8859-1或Windows代码页1252编码的数据。这样的数据通常大部分是ASCII字符,但是也包含了一些超出ASCII定义的0-127范围的字节。UTF-8编码期望这样的字节在指定范围内形成代码单元序列,但在其他编码中,任何字节,无论其值如何,都是一个完整的字符。试图将非ASCII非UTF-8编码的数据解释为UTF-8编码几乎总会导致错误的结果(错误的字符)或根本没有结果(无法解码;解码器返回nil),因为数据从一开始就没有使用UTF-8编码。
首先应尝试使用UTF-8编码,如果失败,则使用ISO 8859-1编码。如果允许用户检索任何网页,请让他们更改用于解码数据的编码方式,以防他们发现它实际上是8859-9或代码页1252或其他8位编码。
如果从特定服务器下载数据,尤其是如果你对该服务器上运行的内容有影响力,应该让它提供准确的Content-Type头和/或修复导致其提供不符合UTF-8编码的文本的任何错误。

这可能是最全面和完整的答案了。为了那些跟随我的脚步在谷歌上搜索这个问题的人,我将把答案提供出来作为答案 :)。总结一下,似乎以UTF解码,并在发生问题时回退到其他编码可能是最好的选择。 - dmkc
不错,实际上NSASCIIStringEncoding像mitjak说的那样工作得很好,但我认为测试几种编码是一个好的做法,以防其中一种失败。我会把这个留给我的IO实用类来保存。 - htafoya
@htafoya: NSASCIIStringEncoding 不应该在任何包含大于127的字符值的字符串上工作,因为这不是有效的ASCII。你应该只会得到 nil。实际上,据我所知,Cocoa将该常量视为ISO 8859-1的同义词。我只能假设苹果之所以没有修复这个问题,是因为存在一些应用程序在表示“ISO 8859-1”时说“ASCII”,这样会在正确行为下出现错误。 - Peter Hosey

9
正如Peter所说,content-type头部只是一个对发送内容的暗示。在服务器端,您可以设置任何内容类型并发送任何字节序列,这可能无效。
我曾遇到处理不正确的UTF-8数据(包括ISO-8859-1(Latin-1)字符(例如法语重音符号))的完全相同问题。
阅读维基百科关于UTF-8以了解此问题及如何处理编码错误是值得的。
事实上, NSString initWithData:encoding:的严格实现仅在发生解码错误时返回nil。(与Java不同,后者使用替换字符)
Peter的将大部分UTF-8数据转换为Latin-1的解决方案并不能令我满意。 (所有UTF-8字符都变得不正确,仅仅因为有一个Latin 1异常字符)
最好的选择当然是在服务器端进行修复,但我没有这方面的责任......
因此,我深入研究,并找到了一种使用GNU libiconv C库(可在OSX和iOS上使用)的解决方案。 原则上是使用iconv删除非UTF-8无效字符(例如,“prété”将变为“prt”)。

这是一个示例代码,相当于命令行iconv -c -f UTF-8 -t UTF-8 invalid.txt > cleaned.txt

#include "iconv.h"

- (NSData *)cleanUTF8:(NSData *)data {
  iconv_t cd = iconv_open("UTF-8", "UTF-8"); // convert to UTF-8 from UTF-8
  int one = 1;
  iconvctl(cd, ICONV_SET_DISCARD_ILSEQ, &one); // discard invalid characters

  size_t inbytesleft, outbytesleft;
  inbytesleft = outbytesleft = data.length;
  char *inbuf  = (char *)data.bytes;
  char *outbuf = malloc(sizeof(char) * data.length);
  char *outptr = outbuf;
  if (iconv(cd, &inbuf, &inbytesleft, &outptr, &outbytesleft)
      == (size_t)-1) {
    NSLog(@"this should not happen, seriously");
    return nil;
  }
  NSData *result = [NSData dataWithBytes:outbuf length:data.length - outbytesleft];
  iconv_close(cd);
  free(outbuf);
  return result;
}

然后,生成的NSData可以使用NSUTF8StringEncoding安全地解码。
请注意,最新的iconv也允许使用回退方法,方法是使用:
iconvctl(cd, ICONV_SET_FALLBACKS, &fallbacks);

通过在 Unicode 错误时使用回退,您可以使用替换字符或更好的方式尝试另一种编码。在我的情况下,当 UTF-8 失败时,我成功地回退到 LATIN-1,这导致了 99% 的正向转换。查看 iconv 源代码以理解它。

我不明白你怎么能够随意丢弃字符?如果你处理的是西里尔字母,会怎样呢?你会把输入中的每个字符都丢弃掉。 - dmkc
我的回答是确保文本有效的UTF-8的一种方式。这就是为什么我发布的iconv代码只会删除无效的UTF-8字符。西里尔字母可以用UTF-8编码,其他编码也可以,但这不是讨论的重点。 - Vincent Guerci
哇!!!谢谢你!!!我一直在为来自我无法控制的服务器的无效UTF-8而苦恼 - 我的NSString总是为空(null) :( 我希望iOS只是像Android的String类一样放置方块或问号之类的东西。再次感谢你!! - fatfreddyscat
顺便提一下,在错误情况下返回 nil 之前,你忘记释放 outbuf 了;你可能想要修复它,这样复制粘贴的人就不会有内存泄漏了(是的,我知道这应该严重不会发生,但这仍然是一个好习惯)。 (哦,还要关闭你的 iconv 句柄) - fatfreddyscat
谢谢!我刚刚在C++中使用ICU将电子邮件转换为UTF8,但无法转换为NSString。我尝试了您的方法,它有效! - InsaneRabbit

5

如果没有指定编码方式,HTTP的默认编码方式是ISO-8859-1。如果HTTP响应符合HTTP/1.1标准且没有指定字符集编码方式,则使用该编码方式。

尝试使用NSISOLatin1StringEncoding解码字符串。


3
数据可能是使用Unicode的另一种编码方式,例如UTF16,或者完全不同的编码方式。
有一些库可以猜测数据使用的编码方式,但这应该是最后的选择。如果您正在使用Web服务,则该Web服务应该有一个文档,说明它使用的编码方式。请查找或询问Web服务提供者使用的编码方式。如果两者都不可用,则应尝试获取样本数据并确定其编码方式,并在程序中使用它。
顺便说一下,从API中提取网页和数据是否是良好的实践,即缓冲数据、转换为字符串,然后在字符串之后进行操作?
这取决于数据的大小。如果数据很小,那么这将是完全可以接受的。如果数据很大,最好逐个处理数据。

它肯定是UTF-8编码。就像某个特定的字符让它出现了问题一样。 - dmkc
你能否发布导致问题的确切字符串?也许它格式不正确等。 - Yuji
这太奇怪了。现在它开始正常工作了..我发现另一个网站也失败了,http://hypem.com。但现在也可以正常工作了..我想怪仿真器或我的网络,但说实话我不知道。。总的来说,既然不是我的设备,可能会导致这样的错误?网络故障可能会产生吗,还是其中一种适当的委托方法会在出错时调用?谢谢你的耐心解答! - dmkc
我猜网站上的数据本身有时会出现损坏,可能是由于一开始未能成功转换为UTF8等原因。编码问题对我来说非常重要,因为我来自日本,那里有三种编码在竞争。虽然不完美,但UTF8的逐步采用对我来说是真正的福音。 - Yuji

0
等一下,原帖首先是从网络上读取内容的对吧?为什么不使用NSString的stringWithContentsOfURL:usedEncoding:error:方法呢?该方法可以通过读取给定URL的数据创建一个字符串,并通过引用返回用于解释数据的编码。
+ (id)stringWithContentsOfURL:(NSURL *)url usedEncoding:(NSStringEncoding *)enc error:(NSError **)error

页面 n 页缩减为一行呵...除非我错了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接