iPhoneとsocket.ioサーバを常時接続させる3

前エントリ iPhoneとsocket.ioサーバを常時接続させる2 からの続きです。
バックグラウンドで接続断が起きた場合の再接続処理を10分に1回実行されるsetKeepAliveTimeoutのタイミングではなく、即時(またはReachabilityが回復次第)に出来ないか調べてみます。

バックグラウンドでもRunLoopを回せるチャンスは?

まずバックグラウンドにおいてRunLoopが回るタイミングを整理します。

  1. voip接続が確立されている場合
  2. setKeepAliveTimeoutが10分1回呼ばれるのでそのタイミングで10秒動く
  3. beginBackgroundTaskWithExpirationHandlerを呼ぶと10分だけRunLoopが動く

1についてはデータの受信をトリガとしてRunLoopが回りますが接続断時は当然回らなくなります。
2はvoip接続を回復できる唯一無二のタイミングとなります。
3はバックグラウンドに回った最初の10分だけの話なので再接続処理としては当てにできません。

Reachabilityが回復したイベントを基点に再接続できないか?

まず考えたのがコレです。
setKeepAliveTimeoutを基点に再接続処理が行われるように、Reachabilityのイベントを基点に再接続処理を実施できないか調べてみました。
ReachabilityのイベントはRunLoopによって通知されるためRunLoopが回っていないとイベント自体が発生しません。つまりバックグラウンド時はReachabilityを失ったというイベントは受信できますが、回復したというイベントは受信できません。

バックグラウンドでも常時RunLoopが回るようにできないか?

次に考えたのがコレです。
なにかのイベントを基点するのではなく単純にバックグラウンドでもRunLoopが回るようにできないか調べてみました。ちなみにバッテリへの影響とかアプリとしてのお行儀とかは一旦忘れて仕組み的に可能かどうかだけに着目します。

beginBackgroundTaskWithExpirationHandlerを呼ぶとバックグラウンドに入った最初の10分間はRunLoopが動きます。
10分間という制限付きですがこの間はフォアグラウンドの時と同じように接続断からの再接続やReachabilityの変化に伴うイベント受信等なんら制約なく動くことができます。
この10分間を増やすというか無制限にできないでしょうか?

beginBackgroundTaskWithExpirationHandlerを呼び直してみる

10分に1度呼ばれるsetKeepAliveTimeoutでbeginBackgroundTaskWithExpirationHandlerを呼び、バックグラウンドタスクの使用を再宣言してみます。

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    void (^handler)(void)  = ^{
        UIApplication* app = [UIApplication sharedApplication];
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    };
    bgTask = [application beginBackgroundTaskWithExpirationHandler:handler];
    [application setKeepAliveTimeout:600 handler:^{
        if (!bgTask || bgTask == UIBackgroundTaskInvalid) {
            UIApplication* app = [UIApplication sharedApplication];
            bgTask = [app beginBackgroundTaskWithExpirationHandler:handler];                
        }
    }];
}

上記のコードを組み込んだ場合の挙動は以下のようになりました。

1. アプリがバックグラウンドに回る

2. beginBackgroundTaskWithExpirationHandlerで10分延命

3. setKeepAliveTimeoutで10分毎に再度beginBackgroundTaskWithExpirationHandlerを呼ぶようにする

4. 10分経過して2で登録したハンドラが実行。バックグラウンドタスクがexpire。

5. 同時にsetKeepAliveTimeoutのハンドラが実行。2に戻る。

バックグランドタスク宣言による10分間の延命要求が10分毎に発行されるので結果的にRunLoopは永続的に有効になります。
これによってフォアグラウンド時と同様に接続断から即時再接続が行えます。またReachabilityの回復時にも即時イベントが通知されるようになります。setKeepAliveTimeoutの10分後を待つ必要はなくなりました。

自分のイメージではbeginBackgroundTaskWithExpirationHandlerはバックグラウンドに一回入る度に一回だけ登録できる代物だと思っていたのですがexpireさえしていれば再度登録できるようです。

うーん、しかしこれはexploitなコードでしょうか。それともiOSのvoipアプリ開発者にとっては定石なんでしょうか。

次回はiPhoneをsocket.ioサーバと常時接続させた際のバッテリの消耗について調べてみたいと思います。