2012年10月20日土曜日

iOSウオッチドッグの存在

今まで全く気にしてなかったが、どうもiOS中にもウォッチドッグタイマーが存在するようである。

ウォッチドッグが何であるか、詳しくは調べてもらうとして、超簡単に書けば、
暴走防止のためのタイマーである。 組み込み機器ではウォッチドッグにひかかったら、
暴走や(周辺デバイス)無反応とみなしリセットをかけるのが普通である。
(正確には、プログラム的に一定時間以内にウォッチドッグタイマーをリセットしないと、ハードウエア的にリセットがかかるようになっている。)

某所の情報によると、iOSでもWebView周りには
  • Watchdog: 20秒(アプリケーション起動時など)
  • DNS: 30秒
  • TCP Connection: 75秒
  • NSURLConnection: 60秒
というウォッチドッグがいるらしい。
"0x8badfood"という例外コードを返してくるとか。"ate bad food"ということらしい。
ateはeatの過去形。発音が8=eightと同じである。

しかし、X-BASIC for iOS開発時の調査の結果、カーネルそのものに関わるタイムアウトも存在ようである。

以下のループをシミュレーター上で実行すると、約256秒でプログラムが落ちる。
while (1) {
#if 0
    NSDate *dt=[[NSDate alloc ]initWithTimeIntervalSinceNow:0.0];
    [[NSRunLoop currentRunLoop]runUntilDate:dt];
    [dt release];
#endif
 }
これで落ちるときはEXC_???(11)(code=0,subcode=0x0)となる。

#ifの中は有効無効どちらにしても変化なし。
また、この中にどれだけ長いCの処理を書いていたとしても、落とされてしまう。
 iOS5.1/6.0とも。

しかもこれ、Xcodeにつないでいる実機上やシミュレーター上でもLeakでトレースしていると発生しない。単独の実機ではHOMEに戻ってしまうし、シミュレーター上で普通に実行している場合は、上記エラーというかトラップを発生して止まる。

2013/11/10追記
iOS7シミュレーターでは発生しない様な気がする。Xcode5で改善された?

・・・

無限ループらしきところを警告しているつもりなのかもしれないが、定期的にcurrentRunLoopに戻しても発生するから、迷惑な事この上ない。
警告ならメッセージで出すべきであって、プログラムを落とすのはやりすぎ。

普通ウォッチドッグタイマーにはそれをリセットする機構があるが、iOSにはない。
ゆえに、iOSは組み込み機器的には使えない。
(iPadやiPod touchが安いから、筐体に組み入れて組み込み機器にしたらいいじゃん、という意見を見たことがあるが、iOSはこの件を含め、組み込み機器向けじゃない。組み込み機器なめんなよってな感じ。)

    ・・・

さらなる調査で、RunLoop内では、如何様にしてもループを回し続けるのはご法度と判明。必ず落ちる。
バッググラウンド内なら回し続けても大丈夫。でもバックグラウンドからからは表示させることができない。というか、表示や入力に関わるすべてのUI要素が使えなくなる。UIWebViewに至ってはレポートまで出力して落ちる。
だが、バックグラウンドからRunLoopに通信する方法が見つからない。
(KVOは同一スレッド内で実行されるのでダメ。 Notificationもおそらく同様。)
これさえあれば何とかならんでもないのだが。

KVOの通知でフラグを立て、先のループ内で表示するという手を考えた。
while (1) {
    NSDate *dt=[[NSDate alloc ]initWithTimeIntervalSinceNow:0.1]; // 0.0はだめ
    [[NSRunLoop currentRunLoop]runUntilDate:dt]
    [dt release];
    if (flag) {
       // 表示
    }
 }
実際にこれで表示が出るには出る。が、秒1回くらいならずっとOKなのだが、秒30回くらいの速度でしばらく回していると表示が出なくなる。どうも、NSRunLoopの行から帰ってこないようだ。RunLoopに入りっぱなしになるという感じ。バグじゃろうこれは。

→アップルのドキュメントの中にRunLoopにスレッドを接続する方法を発見。
何とかUIViewの表示だけは出来るようになった。が、問題はUIViewだけじゃなくUITextFiled等も含む全UI要素に関わるので対応が非常に大変。
→実機で落ちなけりゃそれでOKで無視することにした。


2012年10月12日金曜日

iOS6での画面対応について

iOS6での画面回転対応に非常に苦労したのでここに記録する。
iOS5以前にも対応するなら、従来の回転処理も残しながら、
新たに以下のメソッドを追加する。
細かい話は他のサイトで調べてもらうとして、ここではやり方だけ列記。

(1)基本的に、全てのUIViewControllerに以下の処理を実装する

- (NSUInteger)supportedInterfaceOrientations
// 回転を許可する方向を返す
{
#if 0 // リターン値はこの中から選択
    typedef NS_OPTIONS(NSUInteger, UIInterfaceOrientationMask) {
        UIInterfaceOrientationMaskPortrait,
        UIInterfaceOrientationMaskLandscapeLeft,
        UIInterfaceOrientationMaskLandscapeRight,
        UIInterfaceOrientationMaskPortraitUpsideDown,
        UIInterfaceOrientationMaskLandscape,
        UIInterfaceOrientationMaskAll,
        UIInterfaceOrientationMaskAllButUpsideDown,
    };
#endif
    return UIInterfaceOrientationMaskAll;
}

- (BOOL)shouldAutorotate
// 回転を許可するかどうかを返す
{
    return YES; // NOなら回転させない
}


もし個別のUIViewControllerに実装するのが面倒なら、以下の実装を入れればよい。
(各UIViewControllerの回転時にここが呼び出される。 )

-(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
    return UIInterfaceOrientationMaskAll;
}



(2)[window addSubview:navigationController.view];
ではなく
    [window setRootViewController:navigationController];
に変更する。

ここまででiPadは回転するようになる。iPhone/iPod touchでもPortraitUpsideDown以外は回転するようになるが、PortraitUpsideDownだけが回転してくれない。



(3)UINavigationControllerの中にも上記回転制御を入れる。そのためにサブクラスを作る。


@interface NavigationControllerForiOS6 : UINavigationController
@end

@implementation NavigationControllerForiOS6
- (NSUInteger)supportedInterfaceOrientations
{

    return UIInterfaceOrientationMaskAll;
}

- (BOOL)shouldAutorotate
{
    return YES;
}
@end

UINavigationControllerを使っているところを全て、このNavigationControllerForiOS6
に変更する。



・・・

となる。
(3)がわかりにくいところ。(2)まででiPadまで動いてしまうので見逃しがち。

しかし、アップルはなんでこんな重要な、しかも非常によく使われている部分の仕様を変えたりするんだろうか。その上、変えてなにか良くなっったりしているとも思えないし。
まったく。