2015年6月24日水曜日

Objective-CやiOSの隠れた仕様

X-BASIC for iOS v3.00の開発では、Objective-C、というかそのコンパイラの隠れた仕様に悩まされた部分があった。それを記録しておく。

(1)Objective-Cでの %ld の l判定はlong という宣言文字列と比較されているので実質ではない
何が言いたいのかというと、64ビットでlong != intで弾かれるのはいいとして、
32bitで実質 int == longでも「異なる」と判定されるということ。
X-BASICでは32ビット環境ではLong = longと定義していたのを、64ビット環境でLong = intと変更した。(LLong=long long;64bit)。32ビットのままでなければならない処理が山のようにあるからだ。ところが上記の仕様のため、従来%ldでいけたところをすべて%dにしなければいけなくなった。ところが%dにすると今度は32ビット環境下で警告を食らうハメになる。というわけで、結局都度(int)で型キャストするという手をとった。

(2)Objective-CのC型文字列の"~"はchar *であってsigned char *ではない。
これも警告で引っかかった。
確かにcharはコンパイルオプションでunsigned にもできるけど、でもそうでない設定の時はchar = signed charだろう?これはCの規約違反だと思うのだが。

 (3)定義済み定数__FUNCTION__ は char [n]と定義されるため、”%s”,__FUNCTION__では受けられるけど、func(char *mes)に対してfunc(__FUNCTION__)にすると警告が出る
 わけわからんかもしれないけど、やってみるとわかる。

 (4)NSUserDefaultで、setBoolしたものはintegerForKeyで読める
    [defaults setBool:NO        forKey:@"BOOL"];

    BOOL       ynb=[defaults    boolForKey:@"BOOL"];
    NSInteger yni=[defaults    integerForKey:@"BOOL”];
   どちらでも読めている。

まあ、普通はしないけど、バージョンアップで記録内容をBOOL範囲からNSInteger範囲にしなければならなかったので。

(5)array=[@"A\nB\n" componentsSeparatedByString:@"\x0a"]の結果は
[0]=@"A\n",[1]=@"B\n",[2]=@""となる。
最後に@""が入った要素ができるのが味噌。countで得られる値-1が行数である。

(6)Objective-C(というかそのライブラリ)の%sは日本語に対応できてない。
    NSString *mes=@"English日本語";
    char *cmes=[mes UTF8String];
    NSLog(@"元文字列=%@,char*=%s",mes,cmes);
    NSString *mes2=[NSString stringWithCString:cmes encoding:NSUTF8StringEncoding]; // もしくはmes2=@(cmes)だけでもOK
    NSLog(@"逆文字列=%@",mes2);

を実行すると、
元文字列=English日本語,char*=EnglishÊó•Êú¨Ë™û
逆文字列=English日本語
となる。要するに%sの表示だけがおかしい。NSLog()だけじゃなくてprintf()でも同じ。
日本語を含む文字列の表示には%sを使ってはいけないということ。
バグに近い仕様。

(7)NSStringのlength,substring*のメソッドはサロゲートペア文字を考慮していない
 サロゲートペア文字は常に2文字分の扱いをしないといけない
 サロゲートペアは見た目1文字だけど、内部では2文字の扱いになっているということ。
   length=2だし、substringのrangeもlength=2単位にしないといけない。
X-BASICではV3.10でNSStringをサロゲートペア対応にするためのカテゴリを作って対応した。


(8)NSLog(@"%@",文字列)の時、その文字列がサロゲートペアの最初の半分だった時、表示が全く出ない
これは(7)と絡むのだが、サロゲートペア文字は内部では2文字なので、その最初の1文字だけを持ってNSLog()で表示しようとすると、そのNSLog()すべてが表示されない。
NSLog(@"str=%@",[@"サロゲートペア文字" substringToIndex:1])とかすると、文字だけでなくstr=も含めて表示が出ない。
NSLog()入れてるはずなのに表示が出ないという時はこの可能性がある。

(9)これはObjective-Cではないけど、Zipアーカイブ内のファイル名は、それが作られた環境によって文字コードが異なる様子。
WindowsではSHIFT-JISで格納されている。ZipArchiveというライブラリではUTF8にしか対応していないため、Windows環境下で作られた日本語名ファイル含むZipを展開しようとすると、日本語名ファイルのみ抜けてしまう。
X-BASICではライブラリを修正して利用している。

(10)PNGファイルをiOSのリソースに入れると、ファイルが改変される。
ヘッダーの中にも情報が追加されてる。しかもその追加され方がPNGのフォーマット(規約)に合致していないので、たぶん、そのファイルを抜き出して画像ソフトに読ませても表示できない。
X-BASIC V3.10まででpngHeader()関数を内部にある画像に対して使うと正しい情報が得られないのはこのせい。

(11)iOSでPNGの透過を有効にするときは、256色にするか、インデックスカラーというものにしなければならない。普通にフルカラーで透過色を付けても透過しない。
インデックスカラーへの変換は、フリーのgimpかPhotoshopで出来る。

(12) NSTimerを実行させるにはNSRunLoopへ追加しなければならないが、これはメインスレッドで行わなければならない
NSTimer *tickTimer=[NSTimer timerWithTimeInterval:(NSTimeInterval)MML_1TICK_TIME
  target:self
selector:@selector(tickCountDown)
userInfo:nil
repeats:YES // 繰り返し
  ];

// 次がRunLoopへの追加だが、これはメインスレッドで実行しなければ有効にならない。

[[NSRunLoop currentRunLoop] addTimer:tickTimer forMode:NSDefaultRunLoopMode];

テストでは動いていたものが本番ではどうしても動かないので調べてみたらこれだった。
(X-BASIC'の言語処理は実はバックグラウンドで動いているのです。)
このメソッドはエラーを返さないのでわからなかった。

(13)複数の処理を並行動作させたいときはメインスレッドは使えない
メインスレッドは1つしかないから考えたらあたりまえのことなんだけど、

複数同時に走らせたい処理を
 [self performSelectorInBackground:@selector(fetch_main) withObject:nil];

[self performSelectorOnMainThread:@selector(fetch_main) withObject:nil waitUntilDone:NO];
に変更したらうまく動かなくなった(上記を複数回発行して複数本同時に走らせる)
バックグラウンドは同時に何本でも設定できる=並行動作するが、メインスレッドは1つしかないからである。メインスレッドでは、追加された順にキューに記録され、1つが終わると次のが走る。

0 件のコメント:

コメントを投稿