通信を行うアプリケーションでは、不慮の事態等に備え、タイムアウト処理を実装することが望まれます。特にUIを備えた人間の利用するアプリケーションの場合には、より一層、タイムアウト処理へ気を使う必要があります。
タイムアウト値の選択には、いくつかの異なる視点があります。
例えば、サーバが落ちていたり、サーバまでの経路が切断している場合など、サーバとの接続を確立できない場合は、即座に処理を中断できるよう短いタイムアウトが望まれます。この状況は、待ち時間を長くしても解決できる見込みが少ないためです。
一方、伝送終了までの時間を制限したい場合には、伝送品質や利用回線の状況なども考慮し、先程の例よりも長めのタイムアウト値を設定することが望まれます。この場合、長めの時間を許容することで、通信の終了できる可能性が高まるためです。
iOSやOS XのNSURLRequestでは、初期化時のtimeoutInterval引数によってタイムアウトを設定できます。NSURLRequestを利用するNSURLConnectionクラスは、ここで設定されたタイムアウトを利用します。
iOS5.xまでのNSURLConnectionでは、タイムアウト処理に関し、ドキュメントへ記載されていない制限がありました。それは、HTTP Bodyを持った通信は、NSURLRequestのtimeoutIntervalの値に関わらず、240秒以下のタイムアウトが有効にならない、というものです(この制限を回避するために作成したライブラリが
「NSURLRequestのPOSTでTimeoutを機能させる」で紹介した
TPTimeoutURLConnectionでした)。恐らく、以前はセルラー系伝送速度が特に遅かったこともあり、通信が途中で打ち切られてしまう事態を避けるための仕様だったのだと思います。
この制約はiOS6.xで変更され、240秒以下のタイムアウトも有効になりました。しかし、このタイムアウト値は伝送処理の時間監視に利用されるため、指定時間を過ぎると通信が終了してしまいます。結果、極端に短い値に設定することはできず、通信不能状況、すなわち、接続できない状況の検出目的で使うのは難しい仕様でした。
幸い、iOS7やOS X 10.9で導入された
NSURLSessionでは、この問題に対し、2つのタイムアウト設定を提供する、ということで解決を試みています。
NSURLSessionでは、Sessionの各種設定を
NSURLSessionConfigurationインスタンスを通じて行います。NSURLSessionConfigurationインスタンスでは、timeoutIntervalForRequestとtimeoutIntervalForResourceの2つのプロパティを通じ、2種類のタイムアウトを設定可能です。
timeoutIntervalForRequestは、データ間のタイムアウトを設定するプロパティです。このタイマは新しいデータが到着するとリセットされます。呼の確立までの間や、データが複数個へ分割されている場合など、各データ間のタイムアウト値が設定可能であり、通信が確立できないまま、または切れてしまったまま、長時間待ち状態になる事態を回避できます。
timeoutIntervalForResourceは、従来のNSURLRequestで使われていたtimeoutIntervalに近い機能を持つもので、URLで指定されたデータの取得処理が完了するまでの時間を指定するものです。これは、伝送速度が非常に遅い環境など、通信完了まで非常に長い時間を要してしまう状況を回避するために利用できます。
大変に便利な仕様なのですが、しかし、iOS7.0.3で動作確認を行ったところ、timeoutIntervalForRequestは、データを受信時にタイマがリセットされる状況を作り出すことが出来ませんでした。またその為か、timeoutIntervalForRequestとtimeoutIntervalForResourceの両者を設定した場合、単純に、より短い値がtimeoutIntervalForResource用のタイムアウト値として使われていました。ドキュメントへ書いてある「データ」が、今回の利用想定と異なっているのかもしれません。
さて、NSURLSessionでは、各通信処理をNSURLSessionTask(またはその継承クラス)のインスタンスで取り扱いますが、このTask生成時にNSURLRequestを指定することができます。この場合、NSURLSessionConfigutationによるタイムアウト値と、NSURLRequestによるタイムアウト値が個々に設定可能できることになります。生憎、これらの値の競合に関し、ドキュメント上は記述がありません。
同じくiOS7.0.3で挙動を調査したところ、NSURLRequestへ設定されたtimeoutInterval値は、NSURLSessionでは意味を持たないようです。このtimeoutInterval値が有効なタイムアウト値として利用されることはありませんでした。勿論NSURLSessionConfigurationでタイムアウト値を設定した場合には、その値が利用されます。
今回、上記の調査を行うために、
NetworkTimeoutSampleという小さなアプリケーションと、それと共に用いるサーバアプリケーションを作成しました。
クライアントアプリケーションはiOS7上で動作し、NSURLConnection+NSURLRequest、NSURLSession+NSURLSessionConfiguration、NSURLSession+NSURLSessionConfiguration+NSURLRequestの組み合わせで、各タイムアウトを設定し、動作を確認することを目的としています。
サーバアプリケーションは任意のHTTP要求を受け付け、2行のbodyを持つHTTP 1.0レスポンスを返す処理を行いますが、clientからの接続をacceptする前、HTTP Header送出前、1行目のHTTP Bodyを返す前、ならびに、2行目のHTTP Bodyを返す前、それぞれに秒単位のwaitを挿入することができます。
タイムアウト処理の動作確認等へご利用ください。