NSStringいろいろ

NSStringについてのいろいろ。
記事を書いた時のiOSバージョンはiOS6.1.3です。

初期化時の複数バイトエンコーディングの扱い
- (id)initWithCString:(const char *)nullTerminatedCString encoding:(NSStringEncoding)encoding;
は引数にencodingがあるが、複数バイトエンコーディングのものを指定すると、全て0となっているバイトで文字列末端とみなされるようだ。
例えば2バイトで
00000000 10101010
と表される文字があるとすると00000000のバイトを文字列末端とみなすらしい。
長い文字列のはずが、いきなり初めのほうで文字列が終わってしまう。
+ (id)stringWithCString:(const char *)cString encoding:(NSStringEncoding)enc
も同様。
上位8ビットが0の時だけでなく下位8ビットが0の時でも末端と見るようだ。

おそらく
8ビット毎に取り出して
char selectedChar = 取り出し関数();
if(selectedChar == 0){
  末端処理
}else{
  switch(エンコード){
  各エンコード別の処理
  }
}
のような処理だ。

そもそも8ビットの0を内部に含むエンコーディングはC言語文字列の定義から外れるという考え方なんだろう。

対策は
– initWithBytes:length:encoding:(これはどのエンコードでもいけそう)
- initWithCharacters:length:(UTF-16限定?)
+ stringWithCharacters:length:(UTF-16限定?)
などを使う。

第3水準、第4水準の漢字に対応しているか
まず、漢字の分類方法なのだが、第1、第2、第3、第4とあり、数字が大きいほうに使用頻度の低い漢字が割り当てられている。
第4水準の初めの文字は「毎」のカンムリだけを取り出したようなもの。読み方はわからず。このブログでは表示出来ない。
で、この漢字をiOSで扱えるのか?
Unicodeに含まれていれば対応しているんではないか?と思うかもしれない。
それは確かにそうなのだが、ときどきUnicode以外のエンコードで扱いたい時もある。
その時にちょっと面倒なことが起こる。

この漢字でインスタンスを作り(@"◯")、cStringUsingEncoding:メソッドで各エンコードに変換した値を取り出してみる。

- (const char *)cStringUsingEncoding:(NSStringEncoding)encoding

すると
うまくいったもの
NSUnicodeStringEncoding
NSUTF8StringEncoding
NSUTF16StringEncoding
NSUTF32StringEncoding

NULLが返ってきたもの
NSJapaneseEUCStringEncoding
NSISO2022JPStringEncoding
NSShiftJISStringEncoding

となった。
Unicodeで取り出すとうまくいくのでインスタンスはちゃんと生成されていると思う。
第3水準の「俱」「繫」という字でも同じ結果になった。

第2水準ではどのエンコードでも値が取得できる。

cStringUsingEncoding:メソッドのところにこんなことが書いてあります。
You can use canBeConvertedToEncoding: to check whether a string can be losslessly converted to encoding. If it can’t, you can use dataUsingEncoding:allowLossyConversion: to get a C-string representation using encoding, allowing some loss of information (note that the data returned by dataUsingEncoding:allowLossyConversion: is not a strict C-string since it does not have a NULL terminator).

試しに
canBeConvertedToEncoding:
を各水準の漢字に対して実行すると
NSJapaneseEUCStringEncoding
NSISO2022JPStringEncoding
NSShiftJISStringEncoding
のエンコーディングでは
第1水準 YES
第2水準 YES
第3水準 NO
第4水準 NO
となります。
Unicodeエンコーディングでは全てYES。

で、気になるこのメソッド
- (NSData *)dataUsingEncoding:(NSStringEncoding)encoding allowLossyConversion:(BOOL)flag
説明を読むと、ヨーロッパの文字によくある、文字の上にマークの付いた文字が変換できないときに近い文字に置き換えるもののようです。これが漢字にはどう適用されるか。
allowLossyConversion:にNOを入れた時は第3と第4ではnilが返ってきます。
allowLossyConversion:にYESを入れると第3と第4でもNSDataインスタンスが生成されて返ってきます。

ではそのNSDataには何が入っているのか、
調べてみました。
第1水準 第2水準には求めたい値
第3水準 第4水準には3Fという文字が返ってきました。これは?を意味します。
つまり第3水準 第4水準の漢字ではNSDataインスタンスは生成されますが中身は?という文字ということです。
@"?"に対してメソッドを実行しても3Fが返ってくるので、この仕様はやっつけ仕事というかとりあえずの保留仕様という気もします。

とりあえずここまでのところでは
・Unicodeエンコーディングを使っているうちは第4水準まで扱いがOK
・JIS、シフトJIS、EUCのエンコードに変換が可能なのは第2水準まで
となりました。iOS6.x.xの時点ではこのようですが今後変更になる可能性はあります。

この
- (NSData *)dataUsingEncoding:(NSStringEncoding)encoding allowLossyConversion:(BOOL)flag
ですが、UnicodeのIVSに対応しているのはないかとふと思いました。このIVSというのは一つの文字の微妙に異なるバージョンを区別するものです。
これを確かめるにはまず、渡邊さんの邊で確かめてみます。
①異字体セレクタ無し
②異字体セレクタE0100を付加
③異字体セレクタE0101を付加
の3パターンをflagのYES NO別で行います。(計6パターン)

結果
flagがNOのとき
①NSDataインスタンスは生成されて、中身は0xEEB4の2バイト
②NSDataインスタンスは生成されずにnilが返ってくる
③NSDataインスタンスは生成されずにnilが返ってくる

flagがYESのとき
①NSDataインスタンスは生成されて、中身は0xEEB4の2バイト
②NSDataインスタンスは生成されて、中身は0xEEB43F3Fの4バイト
③NSDataインスタンスは生成されて、中身は0xEEB43F3Fの4バイト
3Fは上のほうにも書いたように?のことで、邊??のような表記になります。

ということでflagがNOの時には異字体セレクタを処理できずnilを返す、YESの時には異字体セレクタに?をあてはめてNSDataを返す。となりました。
ただ全てのパターンで上のようになるかどうかはわかりません。
内部の処理がどうなっているかは正確にはわかりません。
可能性は
①flagがNOだとnilを返し、YESだと異字体セレクタはとにかく外し基本字に対応した文字を探す
②異字体セレクタを認識して、対応する文字を調べるが、なければ①と同じ。flagがNOでもNSDataが生成される可能性あり?
などいろいろ考えられます。


IVS
普通、異字体セレクタは元の字と一緒に使いますが、異字体セレクタのみでNSStringインスタンスを作ったらどうなるかやってみました。

UInt32 ijitaiPrt[1];

ijitaiPrt[0] = 0x000E0100;

NSString *ijitaiStr = [[NSString alloc] initWithBytes:ijitaiPrt length:4 encoding:NSUTF32LittleEndianStringEncoding];
NSLog(@"ijitai %@",ijitaiStr);
NSLog(@"ijitai %d",ijitaiStr.length);

結果は
ijitai 󠄀
ijitai 2
すべてのNSStringの機能がうまくいくかどうかはわかりませんが、lengthがきちんと値が返ってきているのでインスタンスの生成は出来ていると判断出来ます。ちなみに異字体セレクタはUTF16ではサロゲートペアに分解されlengthメソッドでは2文字としてカウントされます。

コメント

このブログの人気の投稿

Swiftのコンパイルエラー寄せ集め

Swift2.2からSwift3.0への変換を行ってみて

AVAudioSession細かいことまとめ(late 2014)