Objective-Cで文字列をUTF-16に変換→復元すると文字化けする

iPhoneアプリを作っているときに、文字列をUTF-16に変換してから復元すると文字化けするという困った現象にぶちあたりました。

下記のコードを実行します。

NSString *str = @"はろーわーるど";

// UTF-16のchar配列を取得
const char *chars = [str cStringUsingEncoding:NSUTF16StringEncoding];

// 得られたchar配列からNSStringを作成
NSString *str2 = [NSString stringWithCString:chars encoding:NSUTF16StringEncoding];

// 出力
NSLog(@"%@", str2);

結果は次の通りです。

漰贰ﰰ輰ﰰ謰椰

見事、文字化けしました。

原因調査 (1) - エンディアンを指定する

Objective-Cで定義されているUTF-16関連の文字コードには、他にも次のようなものが見つかりました。UTF-16に変換する際のエンディアンを指定できるようです(エンディアンとは)。

  • NSUTF16LittleEndianStringEncoding
  • NSUTF16BigEndianStringEncoding

では、NSUTF16StringEncodingの代わりにこれらを使ってみましょう。

NSString *str = @"はろーわーるど";
const char *chars = [str cStringUsingEncoding:NSUTF16LittleEndianStringEncoding];
NSString *str2 = [NSString stringWithCString:chars encoding:NSUTF16LittleEndianStringEncoding];
NSLog(@"%@", str2);

結果は次の通りです。

はろーわーるど

なんと、文字化けしませんでした。NSUTF16BigEndianStringEncodingでも同様でした。どうやらエンディアンがあやしそうです。

原因調査 (2) - NSUTF16StringEncoding時のエンディアンを調べる

では、NSUTF16StringEncodingを指定して変換した文字列を、それぞれのエンディアンを指定して復元してみましょう。

NSString *str = @"はろーわーるど";
const char *chars = [str cStringUsingEncoding:NSUTF16StringEncoding];
NSString *str2 = [NSString stringWithCString:chars encoding:NSUTF16LittleEndianStringEncoding];
NSLog(@"%@", str2);

結果は次の通りです。

はろーわーるど

どうやら、NSUTF16StringEncodingを指定してUTF-16に変換した際にはリトルエンディアンになっているようです。ビッグエンディアンとして復元すると当然ながら文字化けしました。

続いて、それぞれのエンディアンUTF-16に変換したものを、NSUTF16StringEncodingを指定して復元してみましょう。

NSString *str = @"はろーわーるど";
const char *chars = [str cStringUsingEncoding:NSUTF16LittleEndianStringEncoding];
NSString *str2 = [NSString stringWithCString:chars encoding:NSUTF16StringEncoding];
NSLog(@"%@", str2);

結果は次の通りです。

漰贰ﰰ輰ﰰ謰椰

トルエンディアンでは文字化けしました。では、ビッグエンディアンで復元してみましょう。

NSString *str = @"はろーわーるど";
const char *chars = [str cStringUsingEncoding:NSUTF16BigEndianStringEncoding];
NSString *str2 = [NSString stringWithCString:chars encoding:NSUTF16StringEncoding];
NSLog(@"%@", str2);

結果は次の通りです。

はろーわーるど

見事、元通りです。どうやらNSUTF16StringEncodingを指定してUTF-16文字列を復元した際にはビッグエンディアンと解釈されるようです。

結論

Objective-CのNSStringクラスについて、

  • cStringUsingEncodingメソッドにNSUTF16StringEncodingを指定すると、出力文字列はリトルエンディアンとなる
  • stringWithCString:encoding:メソッドにNSUTF16StringEncodingを指定すると、入力文字列はビッグエンディアンと解釈される

ようです。素晴らしい仕様ですね!