web-dev-qa-db-ja.com

NSStringinitWithDataはnullを返します

NSURLConnectionを介してWebサイトからデータを取得し、受信したデータをNSMutableDataのインスタンスに隠しています。 connectionDidFinishLoadingデリゲートメソッドでは、NSStringの適切なメソッドを呼び出して、データを文字列に変換します。

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

結果の文字列はnullであることがわかります。ただし、NSASCIIStringEncodingを使用すると、Unicode文字が期待どおりに文字化けしていても、適切な文字列を取得できます。サーバーのContent-Typeヘッダーはnot UTF-8エンコーディングを指定しますが、同様のシナリオでさまざまなWebサイトを試しましたが、文字列変換は問題なく行われます。問題は特定のWebサービスにのみ関係しているようですが、理由はわかりません。

ちなみに、APIからウェブページとデータを取得することは良い習慣ですか?つまり、データをバッファリングし、文字列に変換し、後で文字列を操作しますか?

大変感謝いたします!

25
dmkc

「間違いなくUTF-8」とおっしゃっていますが、Content-Typeヘッダーがないと、実際にはわかりません。 (そして、それを示すヘッダーがあったとしても、それはまだ間違っている可能性があります。)

私の推測では、データは通常ASCIIであり、常にUTF-8として正しく解析されますが、実際にISO 8859-1またはWindowsコードページ1252でエンコードされているデータを解析しようとしている場合もあります。このようなデータは通常ほとんどASCIIですが、 0〜127の範囲外の一部のバイトASCII定義。UTF-8は、そのようなバイトが指定された範囲のシーケンス内でコードユニットのシーケンスを形成することを期待しますが、他のエンコーディングでは、関係なく任意のバイト非ASCII非UTF-8データをUTF-8として解釈しようとすると、ほとんどの場合、間違った結果(間違った文字)が返されるか、まったく結果が得られません(デコードできません。デコーダーは戻ります)。 nil)、データはそもそもUTF-8でエンコードされなかったためです。

最初にUTF-8を試してみてください。失敗した場合は、ISO8859-1を使用してください。ユーザーに任意のWebページを取得させる場合は、データのデコードに使用するエンコーディングを変更してもらう必要があります。これは、実際に8859-9、codepage-1252、またはその他の8ビットエンコーディングであることがわかった場合に備えてです。

特定のサーバーからデータをダウンロードする場合、特にそのサーバーで実行されるものに影響を与える場合は、正確なContent-Typeヘッダーを提供するようにするか、テキストを提供する原因となっているバグを修正する必要があります。それはUTF-8にはありません。

27
Peter Hosey

Peterが言ったように、content-typeヘッダーは、送信されるコンテンツが期待されるものの単なる「ヒント」です。サーバー側では、任意のコンテンツタイプを設定し、任意のバイトシーケンスを送信できますが、これは無効になる可能性があります。

ISO-8859-1(Latin-1)文字(フランス語のアクセント)を含む誤ったUTF-8データを処理するのとまったく同じ問題がありました。

TF-8に関するウィキペディア この問題とエンコードエラーの処理方法を理解するには、読む価値があります。

事実、NSString initWithData:encoding: strict実装は、デコードエラーが発生したときにnilを返すだけです。 (Javaたとえば、置換文字を使用するものとは異なります)

ほとんどUTF-8データをLatin-1に変換するというピーターの解決策は、私を満足させませんでした。 (ラテン語1の不安定な文字が1つだけの場合、すべてのUTF-8文字が正しくなくなります)

最善の選択肢は確かにサーバー側での修正でしょうが、私はこの側では責任を負いません...

そこで、詳しく調べて、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;
}

次に、結果のNSDataNSUTF8StringEncodingを使用して安全にデコードできます。

最新のiconvでは、以下を使用してフォールバックメソッドも使用できることに注意してください。

iconvctl(cd, ICONV_SET_FALLBACKS, &fallbacks);

Unicodeエラーのフォールバックを使用することにより、置換文字、またはそれ以上を使用して、別のエンコーディングを試すことができます。私の場合、UTF-8が失敗したLATIN-1にフォールバックすることができ、99%の正のコンバージョンが得られました。それを理解するためにiconvソースコードを見てください。

8
Vincent Guerci

何も指定されていない場合のHTTPのデフォルトのエンコーディングはISO-8859-1です。 HTTP応答がHTTP/1.1に準拠していて、文字セットエンコーディングを指定していない場合、それが使用しているエンコーディングです。

そのNSISOLatin1StringEncodingを使用して文字列をデコードしてみてください。

5
JeremyP

データは、UTF16などのUnicodeの別のエンコーディング、またはまったく異なるエンコーディングであった可能性があります。

データで使用されているエンコーディングを推測できるライブラリがありますが、それは最後の手段になるはずです。 Webサービスを使用している場合、そのWebサービスには、使用するエンコーディングを記載したドキュメントが必要です。それを探すか、使用するエンコーディングをWebサービスのプロバイダーに問い合わせてください。どちらも利用できない場合は、サンプルデータを取得してそのエンコーディングを決定し、プログラムで使用する必要があります。

ちなみに、APIからウェブページとデータを取得することは良い習慣ですか?つまり、データをバッファリングし、文字列に変換し、後で文字列を操作しますか?

それはデータのサイズに依存します。それが小さければ、それは完全に問題ありません。大きい場合は、データを少しずつ処理する方がよいでしょう。

3
Yuji

ちょっと待ってください、OPはそもそもネットから読んでいますよね? NSStringのstringWithContentsOfURL:usedEncoding:error:を使用しない理由指定されたURLからデータを読み取ることによって作成された文字列を返し、データの解釈に使用されるエンコーディングを参照によって返します。

+ (id)stringWithContentsOfURL:(NSURL *)url usedEncoding:(NSStringEncoding *)enc error:(NSError **)error

ページnページが1行に減りました...もちろん悲しいことに間違えない限り。

0
Colin