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

前エントリ Objective-Cでsocket.ioクライアントを書いてみたZ Cloudでnodejsを動かすiPhoneアプリとsocket.ioサーバを3G経由で接続させる準備が整いました。
次はiOSネイティブのsocket.ioクライアントの特性を活かしてiPhoneとsocket.ioサーバを常時接続に挑戦してみることにします。
実現したい事は以下の通りです。

  • 普通にiPhoneアプリから3G回線経由でクラウド上のsocket.ioサーバと接続できること
  • 接続を維持できること
    • Homeボタン等を押されてiPhoneアプリが終了(バックグラウンドに回る)しても接続を維持する
    • 圏外などで一度接続を失った場合でも電波受信状況が回復したら自動で再接続する

普通に接続してみる

それでまず普通にsocket.ioサーバに接続するようにNNSocketIOをアプリに組み込んでみます。

__strong NNSocketIO* io;
__weak id<NNSocketIOClient> client;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSURL* url = [NSURL URLWithString:@"http://ホスト名:ポート番号"];
    io = [NNSocketIO io];
    client = [io connect:url];
    [client on:@"connect" listener:^(NNArgs* args) {
        NSLog(@"接続しました");
    }];
    [client on:@"disconnect" listener:^(NNArgs* args) {
        NSLog(@"切断しました");        
    }];
    return YES;
}

このアプリを実機で立ち上げてみるとsocket.ioサーバ側のログに接続完了とハートビートをやりとりしているログが表示されているのでひとまず接続は成功。
でもこの状態で別のアプリを使ったりiPhoneをロックさせたりすると接続は失われてしまいます。これは通常アプリがバックグラウンドで動くことをiOSに認められていないためです。

ちなみにUIApplicationのbeginBackgroundTaskWithExpirationHandlerを呼んで「さーせん!バックグラウンドでももうちょっと生きたいです!」とiOSに宣言すると最大10分の生存期間をもらうことができますが、今回の目標は接続を無制限に維持するという事なので違う方法を探すことにします。

iOSに自分はvoipタイプのアプリです!と宣言してみる

でも実際バックグラウンドで動いているっぽい既成のアプリとかあるんだけどこれはどうなってるの?
ということで調べてみると、iOSではある用途のアプリについては特別にバックグラウンドでも動かせてもらえるようです。用途は音声会話系(voip)、位置情報系(location)、音楽再生系(audio)などが定められておりバックグランドではその用途に応じた範囲で動作することができます。

音声会話系のアプリは着呼や会話などで接続を維持しておく必要があり今回の目的を満たせそうなのでサンプルアプリをvoipタイプと宣言して動かしてみることにします。
アプリのInfo.plistにUIBackgroundModesキーでvoipを指定してvoipアプリであることを宣言。

<key>UIBackgroundModes</key>
    <array>
        <string>voip</string>
    </array>
</key>

ちなみに宣言は自由ですがAppStoreにvoipタイプのアプリとして認められることはまた別の問題です。
今回はもちろん開発用のプロビジョニングですのでAppStoreの制約は気にしないで進めます。

加えて先ほどの初期化コードを以下のように修正します。

    NSURL* url = [NSURL URLWithString:@"http://ホスト名:ポート番号"];
    NNSocketIOOptions* opts = [NNSocketIOOptions options];
    opts.enableBackgroundingOnSocket = YES;
    io = [NNSocketIO io];
    client = [io connect:url options:opts];
    [client on:@"connect" listener:^(NNArgs* args) {
        NSLog(@"接続しました");
    }];
    [client on:@"disconnect" listener:^(NNArgs* args) {
        NSLog(@"切断しました");        
    }];
    return YES;

違いはNNSocketIOOptionsを作成しenableBackgroundingOnSocketプロパティをYESに。最後にconnectメソッドの引数にオプションとして与えています。
こうすることでアプリ内でCFReadStreamRef、CFWriteStreamRefを作成する際にiOSに対して「これはvoip用の接続なんでバッググランドでも切らないでください」と宣言するようになります。

以上でアプリがバックグラウンドに回ってもsocket.ioの接続が切れなくなりました。
ちなみにiOSシミュレータではvoipタイプにしても接続が切れてしまいます。バックグラウンドの挙動については実機を使って確認するしかないようです。
次回はフォアグラウンド/バックグラウンド時における接続断とその回復方法についてメモりたいと思います。