Skip to content

Instantly share code, notes, and snippets.

@shinyaohira
Last active October 5, 2023 07:57
Show Gist options
  • Star 113 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save shinyaohira/5975781 to your computer and use it in GitHub Desktop.
Save shinyaohira/5975781 to your computer and use it in GitHub Desktop.
アプリケーションの状態とマルチタスキング

全体的に簡略化し、必要と思われる部分を抜粋しました。

  • Not running

    アプリは起動されていないか、実行されていたけれどもシステムによって終了されています。

  • Inactive

    アプリはフォアグラウンドで実行中ですが、現在はイベントを受信していません。アプリは、通常別の状態に遷移するときに少しの間だけこの状態になります。

  • Active

    アプリはフォアグラウンドで実行中であり、イベントを受信しています。フォアグラウンドのアプリではこれが通常のモードです。

  • Background

    アプリはバックグラウンドにあり、コードを実行しています。ほとんどのアプリはSuspendedになる過程で少しの間この状態になりますが、追加の実行時間を要求すると、しばらくの間この状態でとどまることができます。

  • Suspended

    アプリはバックグラウンドにありますが、コードを実行していません。システムは、自動的に、そして適切なタイミングで、アプリをこの状態にします。そしてその前に通知することはありません。Suspendedのアプリはメモリ内に保持されますが、コードは一切実行しません。 メモリ不足状態のとき、システムは、フォアグラウンドアプリのための余裕を作るために、事前通知なしにSuspendedのアプリケーションをメモリから消去することがあります。

iOSアプリケーションの状態遷移

アプリが起動し、フォアグラウンド状態に遷移する流れ

Not running -> Inactive -> Active

アプリが起動しフォアグラウンド状態に遷移する流れ

アプリが起動し、バックグラウンド状態に遷移する流れ

Not running -> Inactive -> Background -> Suspended

フォアグラウンド状態に遷移する場合と比べて最も大きな違いは、アプリケーションがActive状態にならず、Background状態になってイベントを処理し、その後すぐにSuspendedになるという点です。この場合もシステムはユーザインターフェイスファイルを読み込みますが、ウインドウは表示しません。

アプリが起動しバックグラウンド状態に遷移する流れ

起動後どちらの状態に遷移するかは、UIApplicationオブジェクトのapplicationStateプロパティをapplication:willFinishLaunchingWithOptions:、またはapplication:didFinishLaunchingWithOptions:デリゲートメソッド内で調べることができます。

起動時に実行するべき処理

アプリの起動時には、フォアグラウンドかバックグラウンドかを問わず、application:willFinishLaunchingWithOptions:メソッドおよびapplication:didFinishLaunchingWithOptions:メソッドで次のような処理をします。

  • 起動オプションの内容をチェックして、アプリの起動理由について調べ、適切に対応する
  • アプリの重要なデータモデルを初期化する
  • ウインドウやビューを表示できるよう準備する

アプリの起動時に、メインストーリーボードやnibファイルを自動的に読み込まない場合は、application:willFinishLaunchingWithOptions:メソッドで、描画に用いるウィンドウを準備してください。また、縦長、横長の表示を使い分けるアプリケーションの場合、メインウィンドウのルートビューコントローラは、常に縦長の状態で設定してください。起動時にデバイスが横向きになっていれば、システムはルートビューコントローラに、ビューを正しい向きに回転してからウィンドウに描画するよう指示します。

application:willFinishLaunchingWithOptions:およびapplication:didFinishLaunchingWithOptions:メソッドは、できるだけ軽い実装にして起動時間を短縮しなければなりません。起動後、必要な初期化を行い、イベント処理を開始するまで、5秒以内で完了するようにしてください。それ以上かかる場合、システムは応答がないものと判断し、アプリケーションを強制的に終了してしまいます。迅速な起動処理の妨げになる処理(ネットワークアクセスなど)は、スレッドを生成し、非同期に実行するようにします。

起動後フォアグラウンドに移行する場合、システムは最後にapplicationDidBecomeActive:メソッドを呼び出して、フォアグラウンド状態への遷移処理を終えます。このメソッドは起動時だけでなく、バックグラウンド状態から遷移するときにも呼び出されるので、両方に共通の処理を記述してください。

電話がかかってきた場合など、アラートベースの割り込みが発生すると、アプリは一時的にInactiveになり、システムはその後の処理をどうするかユーザに問い合わせます。この状態はユーザがアラートを消すまで続きます。その後、アプリケーションはActiveに復帰するか、またはBackgroundになります。

アラートベースの割り込みの処理

バナー通知が表示された場合、アプリはInactiveにはならず、タッチイベントを受け取ることができます。しかし、ユーザーがバナーを引き下ろして通知センターを表示すると、アプリはInactiveになります。

割り込み発生時に必要な処理

アラートベースの割り込みがあると、アプリによる制御が一時的に失われます。そのままフォアグラウンド状態で動作し続けますが、タッチイベントは送られてこなくなります(通知やほかのタイプのイベント(加速度センサーイベントなど)はそれまで通り受け取れます)。このときに呼び出されるapplicationWillResignActive:メソッドでは、次のような処理をしなければなりません。

  • タイマーその他、周期的に起こるタスクを停止する
  • メタデータクエリが動作していれば停止する
  • 新たなタスクを起動しない
  • 動画の再生を停止する(AirPlay経由で再生している場合を除く)
  • アプリケーションがゲームの場合、一時停止状態にする
  • OpenGL ESのフレームレートを落とす
  • 重要なコードを実行していないディスパッチキューやオペレーションキューを停止する(ネットワーク要求など、時間を要するバックグラウンドタスクは、Inactiveでも続行可)

アプリがActiveに戻るとapplicationDidBecomeActive:メソッドが呼び出されるので、ここでapplicationWillResignActive:メソッドとは逆の手順で復帰処理を行います。つまり、タイマーを再起動し、ディスパッチキューを再開し、OpenGL ESのフレームレートを元に戻します。ただしゲームは自動的に再開しないでください。ユーザが操作をするまで、停止状態のままにしておかなければなりません。

保護オプションNSFileProtectionCompleteを設定してファイルを保護しているアプリは、ユーザがスリープボタンを押した場合、当該ファイルへの参照を閉じなければなりません。パスワードが設定されたデバイスでは、スリープボタンを押すと画面がロックされ、システムは、この保護オプションが設定されたファイルの復号キーを破棄します。画面がロックされている間、当該ファイルにはアクセスできません。したがって、該当するファイルがある場合、applicationWillResignActive:メソッド内で当該ファイルへの参照を閉じ、applicationDidBecomeActive:メソッド内で改めて開く必要があります。

ユーザがホームボタンやスリープボタンを押したり、システムが別のアプリを起動した場合、フォアグラウンドのアプリケーションはInactiveに遷移し、次にBackgroundに遷移します。

フォアグラウンドからバックグラウンドへの移行

バックグラウンドに移行する際に必要な処理

applicationDidEnterBackground:メソッドに、Backgroundに移行するための準備処理を記述することができます。バックグラウンドに移行する際には、次のような処理が必要です。

  • スクリーンショットを撮れるように準備する

    applicationDidEnterBackground:メソッドが復帰したときに、システムはアプリのユーザインターフェイスの画像を撮って、その画 像を遷移のアニメーションに使用します。インターフェイス内のビューに機密情報が含まれている場合は、applicationDidEnterBackground:メソッドが戻る前に、それらのビューを隠すか修正する必要があります。

  • ユーザデータとアプリケーションの状態情報を保存する

    すべての未保存の変更は、バックグラウンドに入るときにディスクに書き込む必要があります。これが必要なのは、さまざまな理 由で、バックグラウンドになっている間に、アプリが予告なしに強制終了される場合があるからです。

  • メモリをできる限り解放する

    アプリは、バックグラウンド状態に移行する際、メモリをできる限り解放しなければなりません。システムはメモリ上に、できるだけ多くのアプリを置いておこうとしますが、空きメモリ量が減ってくると、一時停止状態のアプリケーションを停止し、メモリの確保を試みます。バックグラウンド状態であって、大量のメモリを消費しているアプリは、優先的に停止してしまいます。

デリゲートのapplicationDidEnterBackground:メソッドは、任意のタスクを終了して戻るまでに約5秒かかります。実際には、このメソッドはできるだけ早く戻らなければなりません。このメソッドがタイムアウトの前に戻らないと、アプリは強制終了され、メモリから削除されます。タスクを実行するためにそれより長い時間を必要とする場合は、beginBackgroundTaskWithExpirationHandler:メソッドを呼び出して、バックグラウンドの実行時間を要求し、長時間実行するタスクを別のスレッドで起動します。バックグラウンドのタスクを起動するかどうかに関わらず、applicationDidEnterBackground:メソッドは5秒以内に実行しなければなりません。

バックグラウンドからフォアグラウンドへの遷移

フォアグラウンド状態に復帰すると、以前バックグラウンド状態に移行する際に停止した処理を再開できます。applicationWillEnterForeground:メソッドでは、applicationDidEnterBackground:メソッドでの処理を取り消して元の状態に戻すことになります。applicationDidBecomeActive:メソッドでは、起動時と同じ、アクティブ化の作業を行います。

キューに入っていた通知の処理(ウェイクアップ時)

Suspendedの間にイベントが発生すると、アプリはその通知をキューに入れておき、その後フォアグラウンドまたはバックグラウンド状態になった時点で、まとめて処理します。 アプリケーションが再開したときにたくさんの通知でアプリケーションに過剰な負担がかかるのを避けるため、システムは通知をまとめます。そして、アプリケーションが一時停止してからの実際の変更に相当する、1つのイベント(関係する種類ごとに)を通知します。

その他、必要に応じてiCloudの状態変化の対応、ロケール変更への対応、アプリ設定の変化への対応などを行う必要があります。

アプリのメイン実行ループは、ユーザが関与するイベントをすべて処理します。UIApplicationオブジェクトは起動時にメイン実行ループをセットアップし、これを使ってイベントを処理したり、ビューベースのインターフェイスを更新したりします。名前からも分かるように、ア プリのメインスレッド上で動作します。したがって、ユーザが関与するイベントは、受け取った順で直列に処理されます。

メイン実行ループでのイベント処理

iOSアプリケーションに配信されるイベントには、さまざまな種類があります。よく現れるイベントは次のものです。

  • Touch

    ビューがレスポンダオブジェクトとして応答します。ビューが直接処理しない場合は、レスポンダチェーンに沿って順次配信されます。

  • Remote control

    メディアの再生を制御するイベントで、ヘッドフォンその他のアクセサリが生成します。

  • Motion

    (デバイスを振るなど)特殊な動きに関係するイベントで、加速度センサーに関係するほかのイベントとは別に処理されます。

  • Accelerometer Core Motion

    加速度センサーやジャイロスコープといったハードウェアに関係するイベントは、開発者が指定したオブジェクトに配信されます。

  • Redraw

    イベントオブジェクトとしては現れず、単にビューが自分自身を描画するメソッドを呼び出すというものです。

  • Location

    位置イベントを受け取る旨の登録にはCore Locationフレームワークを使います。

バックグラウンド状態に遷移しつつあるアプリは、最後に重要な処理をしなければならない場合、実行に必要な時間を要求できます。 バックグラウンドで実行する時間を要求する場合は、UIApplicationクラスのbeginBackgroundTaskWithExpirationHandler:メソッ ドを呼び出します。アプリが作業中にバックグラウンド状態に移行するときだけでなく、すでにバックグラウンド状態である場合も、このメソッドで、Suspendedになるまでの時間を延長できます。ユーザデータをディスクに書き込んでいたり、重要なファイルをネットワークサーバからダウンロードしている場合など、中断しては困る重要な作業がある場合にこの機能は重要です。

beginBackgroundTaskWithExpirationHandler:メソッドを呼び出し、作業が終わったらendBackgroundTask:メソッドを呼び出します。 アプリにはバックグラウンドタスクを終了するために限られた時間しか与えられていないため、時間切れになりシス テムがアプリを強制終了する前に、このメソッドを呼び出さなければなりません。 強制終了を避けるために、タスクを開始するときに時間切れハンドラを提供し、それが呼ばれた時にendBackgroundTask:メ ソッドを呼び出すこともできます。

バックグラウンド処理中に問題が発生した場合は、通知機能を利用してユーザーに通知してください。

通知は、Suspended、Background、Not runningの状態のアプリがユーザの注意を喚起する手段です。 たとえば目覚まし時計アプリは、この仕組みを使ってアラームを鳴らし、止めるためのアラートを画面に表示します。

ローカル通知を送信予約するには、UILocalNotificationクラスのインスタンスを作成し、通知パラメータを設定し、UIApplicationクラスのメソッドでスケジュール設定します。ローカル通知オブジェクトには、送信する通知の種類(サウンド、アラート、バッジ)に関する情報と、送信する時刻(必要であれば)が含まれます。

タスクがもっと実行時間を必要とする場合は、Suspendedにならずバックグラウンドで動作するための特別な許可を要求する必要があります。iOSでバックグラウンド動作が許されるのは、次の種類のアプリだけです。

  • ミュージックプレーヤーアプリのように、バックグラウンドで音声を再生するアプリ
  • バックグラウンドでも位置情報をユーザーに通知し続けるアプリ
  • VoIP(Voice over Internet Protocol)をサポートするアプリ
  • 最新号をダウンロードして処理する必要があるNewsstandアプリ
  • 外付けアクセサリから定期的に更新情報を受け取るアプリ

アプリケーションに実装されたバックグラウンドタスクの宣言

バックグラウンドタスクの実行をサポートする場合は、その宣言をInfo.plistファイルに記述する必要があります。Info.plist ファイルにUIBackgroundModesキーを追加し、以下に示す文字列の配列として値を設定してください。

  • audio
  • location
  • voip
  • newsstand-content
  • external-accessory
  • bluetooth-central
  • bluetooth-peripheral

ユーザの位置情報の追跡

高精度の位置情報を必要としないアプリに対しては、significant-change location serviceを強く推奨します。このサービスを利用すると、位置の更新情報は、ユーザの位置が大幅に変化したときにのみ生成されます。そのため、SNSやクリティカルではない位置関連情報をユーザに提供するアプリに適しています。更新が生じたときにアプリがSuspendedだった場合、システムはアプリをバックグラウンドで起動します。

位置情報サービスは、iOSデバイスに内蔵の無線ハードウェアを活発に使用するため、大量の電力を消費します。アプリが精密で連続的な位置情報をユーザに提供する必要がない場合は、位置サービスの使用を最小限に抑えるようにしてください。

バックグラウンドオーディオの再生

音楽の再生を開始した後、バックグラウンドに移るアプリケーションは、オーディオ出力バッファが満たされるまでさらに実行時間が必要です。audioキーを追加することにより、システムフレームワークに対して、アプリは再生を続ける必要があり、適切な間隔でアプリへのコールバックを呼び出す必要があることを伝えることができます。このキーが追加されていないと、再生中のオーディオは、アプリがバックグラウンドに入ると停止します。

バックグラウンドオーディオアプリとしては、次のようなものがあります。

  • 音楽プレーヤーアプリ
  • AirPlay上でオーディオ/ビデオを再生するアプリ
  • VoIPアプリ

バックグラウンドオーディオ再生を開始するには、どのシステムオーディオフレームワークを使用しても構いません。また使い方も変わりません。AirPlayでビデオを再生する場合、ビデオを表示するためにMedia PlayerやAV Foundationフレームワークを使用することができます。メディアファイルを再生している間、アプリはSuspendedではないため、アプリがバックグラウンドにある間もコールバックは通常通り行われます。しかしコールバック内では、再生データの供給に必要な処理だけを行うようにしてください。たとえば、ストリーミングオーディオアプリは、サーバから音楽ストリームをダウンロードし、現在のオーディオサンプルを送り出す必要があります。再生に関係ない余分な仕事は実行するべきではありません。

1つ以上のアプリがオーディオをサポートできるため、システムはどのアプリがオーディオを再生できるかを制限します。フォアグラウンドアプリは、常にオーディオを再生する権限を持ちます。さらに、1つ以上のバックグラウンドアプリも、オーディオセッションの設定に応じて、オーディオコンテンツを再生できます。常にアプリのオーディオセッションオブジェクトを適切に設定し、システムフレームワークを注意深 く使用して、割り込みやその他のオーディオ関連通知を処理しなければなりません。バックグラウンド実行用にオーディオセッションオブジェクトを設定する方法の詳細については、Audio Session Programming Guideを参照してください。

フォアグラウンドのアプリは、バックグラウンドアプリに比べ、常にシステムのリソースやハードウェアの使用に対する優先権を持っています。バックグラウンド状態になったら、アプリはこれを考慮していくつか動作を変える必要があります。具体的には、バックグラウンドに移行するアプリは以下のガイドラインに従う必要があります。

  • コードからOpenGL ESの呼び出しは行わない

  • Suspendedになる前に、Bonjour関連のあらゆるサービスをキャンセルする

  • ネットワークベースのソケットで接続失敗に対応できるよう準備する

    アプリがSuspendedの間、システムはソケット接続を破棄する可能性があります。ソケットベースのコードが、電波のロストやネットワークの移行など、さまざまなネットワーク障害に備えている限り、これが特別に問題につながることはありません。アプリが復帰したときに、ソケットの使用に障害が発生した場合は、単に再接続してください。

  • バックグラウンドに移行する前にアプリの状態を保存する

    メモリ不足状態のとき、空き領域を作るために、バックグラウンドのアプリがメモリから削除される場合があります。Suspendedのアプリが先に削除され、削除の前にアプリに通知はされません。そのため、アプリiOS 6以降で提供される状態保持機構を活用して、インターフェイスの状態をディスクに保存する必要があります。

  • バックグラウンドに移行するとき、不要なオブジェクトの強い参照を削除する

    アプリが、オブジェクト(特に画像)のメモリ内キャッシュをたくさん保持している場合は、バックグラウンドに移るときに、これらのキャッシュのstrong参照をすべて削除することを検討するべきです。

  • 一時停止の前に共有システムリソースの使用を止める

    連絡先やカレンダーなど、共有のシステムリソースを扱う場合、Suspendedになる前にこれらのリソースの使用を停止する必要があります。このようなリソースに対する優先権は常にフォアグラウンドアプリにあります。アプリが一時停止の状態にあるときに、共有リソースの使用が検出されると、アプリは強制終了されます。

  • ウインドウとビューの更新を避ける

    バックグラウンドでは、アプリのウィンドウとビューは表示されないため、更新を試みるべきではありません。バックグラウンドでウィンドウオブジェクトやビューオブジェクトを作成したり操作したりしても、アプリが強制終了させられることはありませんが、こうした処理はアプリがフォアグラウンドに戻るまで延期するべきです。

  • 外部アクセサリのために接続通知と切断通知に対応する

  • バックグラウンドに移行するときに、アクティブアラートのためのリソースをクリーンアップする

    アプリ間を切り替える際にコンテキストを保存しておくために、アプリがバックグラウンドに移行しても、システムは自動的にアクションシートやアラートビューを閉じません。バックグラウンドに移行する前に適切なクリーンアップ処理を行うのは開発者の仕事です。たとえば、アクションシートやアラートビューをプログラミングによってキャンセルしたり、(アプリケーションが終了させられた場合に)後でビュー を復元するためにコンテキスト情報を保存する必要があるかもしれません。

  • バックグラウンドに移行する前に、ビューから機密情報を削除する

    アプリがバックグラウンドに移行するときに、システムはアプリのメインウィンドウのスナップショットを取ります。アプリがフォアグラウンドに戻るときに、このスナップショットが一時的に表示されます。applicationDidEnterBackground:メソッドから復帰する前に、スナップ ショットの一部としてキャプチャされる可能性のある、パスワードなどの機密性の高い個人情報は、非表示にするか隠しておく必要があります。

  • バックグラウンドで実行している間は最小限の処理のみ行う

システムはアプリのメインスレッドを生成しますが、アプリは必要に応じてそれとは別にスレッドを生成し、ほかのタスクを実行することができます。スレッドの生成方法としては、Grand Central Dispatch(GCD)キューやオペレーションキューを使って、システムにスレッドを生成させるやり方を推奨します。どちらのタイプのキューもタスクの非同期実行モデルを提供します。タスクをキューに登録すると、システムはスレッドを生成し、その上でタスクを実行します。スレッド管理をシステムに任せることで、コードが簡潔になり、システムにとってもスレッドを最も効率的に管理することができます。

メインスレッド以外で実行できる処理は、できるだけキューを使って分離してください。メインスレッドではタッチイベントや描画イベントを処理しなければならないので、時間のかかるタスクをここで実行するべきではありません。たとえばネットワークの応答を待機するような処理を、メインスレッド上で実行してはいけません。キューを使って非同期に実行し、終了後に結果を処理するようにしてください。

他にタスクを副スレッドに移す良いタイミングは起動時です。起動したアプリが初期処理を行いイベントを処理を開始するのに限られた時間(約5秒)しかありません。もし先送りできたり副スレッドで処理可能な初期処理がある場合は、メインスレッド以外に移し、メインスレッドではユーザインターフェイスの表示やイベント処理の開始だけを行うようにします。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment