不要なプロセスは自分で選んで終了させよう

iOS4から、一部の古い機種を除き、マルチタスキングがサポートされるようになりました。それによって、アプリの切替が速くなったのはいいですが、反面、待機しているバックグラウンドプロセスが増えたことにより、メモリ使用量が増えたという問題があります。
そんな背景から、iOS4では空きメモリが少なくなることは日常的に起こることなので、その都度再起動して空きメモリを増やすというのは効率が悪く、面倒なことです。
では、iOS4からサポートされた、"First App Switching"は、どうでしょう?
ホームボタンのダブルクリックで、「最近使ったアプリケーション」のアイコンリストを表示してくれます。

すでにご存知の方も多いと思いますが、このアイコンには、「アプリの切替または起動のためのショートカット」以外に、アイコンの長押し後表示される"-"をタップすることで、アイコンを削除するとともに、そのアプリのプロセスが存在していれば終了するという機能を持っています。

せっかく用意されている機能なのに、残念ながら、自分でプロセスを選んで終了させるのを面倒だと感じる方も多いようです。でも慣れてくれば、どれを終了させればいいのか見当がつくようになるので、それほどの手間ではないと思います。特に、コンピュータに詳しい上級ユーザの方であれば、難なくこなせることでしょう。

ここでは、SysStats Monitorを、この「最近使ったアプリケーション」のアイコンリストと組み合せることで、終了させると効果があるプロセスの見当をつける方法を紹介してみたいと思います。

注:
今回は3GSでテストしています。他の機種には当てはまらないこともあります。
プロセスのメモリサイズは、そのアプリの使用状況によってことなるので、常に、ここで示した通りになるわけではありません。

システムの状態を確認する

動きが重いなどの異状を感じた場合、とりあえずSysStats Monitorを起動してみましょう。例えば、以下のような状態であれば、かなりメモリが逼迫していますね。

以下のようなプロセスが、バックグラウンドに存在しているようです。


プロセスを終了させてみる

このまま使っていても問題はないのですが、気になる場合は、不要なアプリのプロセスを終了させてみましょう。

連絡先


3MBほど、空きメモリが増えましたが、効果は薄い感じです。

Safari


37MBほど、空きメモリが増えました。これは効果がありそうですというか、織込み済みですね。
ここまで空きメモリが増えれば、ほとんどの場合充分ですが、テストのために、もう少しやってみます。

バックグラウンドに存在していなかったアプリ

ここで、バックグラウンドプロセスとして存在していなかった、Facebookを削除してみます。

ほとんど変化なしです。恐らくあまり削除する意味は無いでしょう。

Twitter for iPhone

さらに続けて、バックグラウンドに存在していたTwitterを削除してみます。

7MBほど、増えましたが、効果は中途半端な感じですね。

終了したプロセスを確認する。

最後に、この間に終了されたプロセスを確認してみましょう。

ここまでの操作で終了したアプリのプロセスが表示されていますね。

注: 上記の一連の操作の間、SysStats Monitorが起動されたままになっていることが前提です。


こんな感じで調べてみることで、自分なりのプロセス終了パターンを見つけられると思います。
興味がある方はお試しください。

iPhoneの「電源オフ」と「リセット」は何が違うのか?

iPhoneの調子が悪いときに、システムを再起動することで問題が解決することが多いのは、みなさんがご存知だと思います。

iPhoneユーザガイドの226ページにある、「付録 A: サポートおよびその他の情報」の「iPhone を再起動する/リセットする」には、具体的な操作方法が説明されていますが、「電源オフ」→ 「電源投入」の流れを「再起動」と呼んでいるようです。
この「再起動」と「リセット」にはどのような違いがあるのかについて、考えてみました。

安全性について

このユーザガイドの説明で、1つ気になるのは、以下の記述です。

iPhone の電源を切ることができない場合や、問題が解決しない場合は、iPhone のリセットが必要 な場合があります。 リセットは、iPhone の電源を切って入れ直しても問題が解決しない場合にのみ 実施してください。

これは暗黙的に、「再起動」の方が安全であることを示唆していますね。実際にやってみると、「リセット」のときは瞬断されている感じですが、「電源オフ」は何か処理をしてから電源が切れているように見えます。

起動された後にはどのような違いがあるのか?

SIMの状態

電源オフ→電源オンの流れで行われる「再起動」のときは、SIMがロックされた状態になるので、PINを入力しないと電話回線が使用できません。

やはり、電源オフをするかどうかで、ハードウェアレベルでは何らかの違いが出てくるというのは、当然かもしれませんね。

空きメモリの状態

これは、諸説あるようですが、正直なところ、違いはないと言っていいと思います。
どちらも、OSレベルでは、新しくブートされた状態になります。
以下に、それぞれの起動直後のメモリの状態を示します。

  • リセット時


  • 再起動(電源オフ→電源オン)

確かに、この数値を見ると、約8MBの差がありますが、正直なところこれは誤差の範疇だと思います。OSが起動するときは、いろいろな処理が行われ、バックグラウンドいくつかのプロセスが起動されたり終了されたりしています。見るタイミングによって、この程度の違いが出ることは、ふつうに想定されることですよね。
また、OS起動時に行われる処理がすべて完了し、システムが落ち着いた状態にこの程度の差があったとしても、気にするようなものではないでしょう。


以上、簡単にまとめてみましたが、いかがでしょうか?
少し手順は面倒でも、「再起動(電源オフ→電源オン)」にしておいた方が、よさそうな感じですね。

空きメモリが少ない時に、iPhoneの中で何が起きるのか、また調べてみました。

一般的なコンピュータは、物理的に空きメモリが不足すると、OSの判断で、メモリ上のデータを、スワップ領域に逃がして、空きメモリを供給しようとします。iOSの場合は、スワップ領域がないので、また異なった手法が取られます。

Out Of Memory Killerに似てるけど、もう少しやさしいiPhoneOSの空きメモリー制御 - The iPhone Development Playground

また、過去に、iPhoneOS2.xの頃ですが、以下のようなことも書きました。

iPhoneのSafariがいつのまにか終了する条件 - The iPhone Development Playground

iOS4になって、バックグラウンド化されるアプリが激増したことにより、少しやり方が変わったようなので、同じように、また調べてみました。

テストケースについて

今回は、以下の手順でテストしてみました。

  • 標準の状態で、ホーム画面の1ページ目にあるアプリと、Dockに置かれるアプリをすべて起動する。この時点で、空きメモリはかなり少なくなっている。

  • 標準のカメラアプリで写真を1枚撮る。

標準のカメラアプリを選んだ理由は、単純にメモリ使用量が多そうだというだけです。

実際に起こったこと

上記のテストケースを実行する中で、実際起こったことをiPhoneのConsoleログで確認しました。*1

まずは、カメラアプリ自体がmemory warningを受取る。

Thu Jul 22 21:14:26 <> MobileSlideShow[101] : Received memory warning. Level=2

launchd*2が、いくつかのプロセスをkillする。これは、否応無しの強制終了ですね。

Thu Jul 22 21:14:27 <> com.apple.launchd[1] (UIKitApplication:com.apple.stocks[0x51a2][555]) : (UIKitApplication:com.apple.stocks[0x51a2]) Exited: Killed
Thu Jul 22 21:14:27 <> com.apple.launchd[1] (UIKitApplication:com.apple.Maps[0x9f0a][83]) : (UIKitApplication:com.apple.Maps[0x9f0a]) Exited: Killed
Thu Jul 22 21:14:27 <> com.apple.launchd[1] (UIKitApplication:com.apple.AppStore[0x4978][559]) : (UIKitApplication:com.apple.AppStore[0x4978]) Exited: Killed
Thu Jul 22 21:14:27 <> com.apple.launchd[1] (UIKitApplication:com.apple.weather[0xe312][562]) : (UIKitApplication:com.apple.weather[0xe312]) Exited: Killed
Thu Jul 22 21:14:27 <> com.apple.launchd[1] (UIKitApplication:com.apple.mobiletimer[0xc065][563]) : (UIKitApplication:com.apple.mobiletimer[0xc065]) Exited: Killed
Thu Jul 22 21:14:27 <> com.apple.launchd[1] (UIKitApplication:com.apple.calculator[0x83f4][564]) : (UIKitApplication:com.apple.calculator[0x83f4]) Exited: Killed
Thu Jul 22 21:14:27 <> com.apple.launchd[1] (UIKitApplication:com.apple.mobilenotes[0x53bc][565]) : (UIKitApplication:com.apple.mobilenotes[0x53bc]) Exited: Killed
Thu Jul 22 21:14:28 <> com.apple.launchd[1] (UIKitApplication:com.apple.Preferences[0x3c08][566]) : (UIKitApplication:com.apple.Preferences[0x3c08]) Exited: Killed
Thu Jul 22 21:14:28 <> com.apple.launchd[1] (UIKitApplication:com.apple.MobileAddressBook[0x8471][568]) : (UIKitApplication:com.apple.MobileAddressBook[0x8471]) Exited: Killed
Thu Jul 22 21:14:28 <> com.apple.launchd[1] (UIKitApplication:com.apple.VoiceMemos[0x2037][569]) : (UIKitApplication:com.apple.VoiceMemos[0x2037]) Exited: Killed

SpringBoardが、アプリがkillされたことを検知する。

Thu Jul 22 21:14:27 <> SpringBoard[28] : Application 'マップ' exited abnormally with signal 9: Killed
Thu Jul 22 21:14:28 <> SpringBoard[28] : Application '株価' exited abnormally with signal 9: Killed
Thu Jul 22 21:14:28 <> SpringBoard[28] : Application 'App Store' exited abnormally with signal 9: Killed
Thu Jul 22 21:14:28 <> SpringBoard[28] : Application '天気' exited abnormally with signal 9: Killed
Thu Jul 22 21:14:28 <> SpringBoard[28] : Application '時計' exited abnormally with signal 9: Killed
Thu Jul 22 21:14:29 <> SpringBoard[28] : Application '計算機' exited abnormally with signal 9: Killed
Thu Jul 22 21:14:29 <> SpringBoard[28] : Application 'メモ' exited abnormally with signal 9: Killed
Thu Jul 22 21:14:29 <> SpringBoard[28] : Application '設定' exited abnormally with signal 9: Killed
Thu Jul 22 21:14:29 <> SpringBoard[28] : Application '連絡先' exited abnormally with signal 9: Killed
Thu Jul 22 21:14:29 <> SpringBoard[28] : Application 'ボイスメモ' exited abnormally with signal 9: Killed

CrashReportが生成される。

これは、「Low Memory」というタイプのCrashReportですね。ふつうに使っているだけでも、そこそこ出ると思います。
今のところ、この「Low Memory」のCrashReportを出なくする方法はないと思います。

Thu Jul 22 21:14:30 <> ReportCrash[594] : Saved crashreport to /Library/Logs/CrashReporter/LowMemory-2010-07-22-211430.plist using uid: 0 gid: 0, synthetic_euid: 0 egid: 0

生き残ったアプリはどれか?

とりあえず、以下のアプリのプロセスは、killされずに残っていました。どういう基準でkillされるものが選別されたのかは不明ですが、恐らく、suspend状態のものが、優先的にkillされているのでしょうね。残ったものは、何となくsuspend状態ではないような気がします。

空きメモリはどうなったか?

プロセスが強制終了されたことによって、空きメモリは増えました。当然と言えば、当然の結果ですが。。

まとめ

このように、ごく普通の状況で、バックグラウンド化されてsuspend状態のアプリは、強制終了される可能性があります。裏を返せば、いつ強制終了されても、問題が起きないようにしておくことが重要だということですね。
あと、ユーザの立場からは、こういう形で強制終了されたアプリは、「最近使ったアプリケーション」のアイコンリストからは消えるわけではないことも、理解しておく必要があると思います。

そのほかに気になるところは、バックグラウンドでひっそりと動きながら、位置情報のトラッキングや音楽の再生をしているアプリが、こういう状況でどうなるのかということですね。また、機会があれば、調べてみたいと思います。
(そういうタイプのアプリは、常にいきなり強制終了ではないように思いますが、まだ確信はない状態です。。)

最後に。。

過去の記事で、「メモリ解放(?)機能」について、いろいろ試行錯誤したこともあり、誤解されている方もいらっしゃるようですが、私のアプリでは、そのような機能を実装する予定はありません。ご理解、ご了承のほどお願いいたします。

*1:Consoleログは、アプリ開発環境であるXcodeを使うことで見ることができます。

*2:プロセスの起動を制御するデーモンプロセス。Undocumented Mac OS X:第1回 initを置き換えるlaunchd【前編】 (2/3) - ITmedia エンタープライズを参照

バックグラウンドプロセスが多い時のメモリ使用状況の不思議

SysStats Monitor/SysStats Liteでは、host_statistics()という関数をHOST_VM_INFO指定で呼び出して得られるvm_statistics構造体から、現在のメモリ使用状況を取得して表示しています。アプリをたくさん起動して、バックグラウンドプロセスが多い状態を作ると、以下のような感じになります。

この中で、実際にvm_statistics構造体から取得できる値は空き、現在非使用中、現在使用中、固定中の4項目のみです。最も大きな面積を占めている「その他」は、物理RAM容量(この場合は253MB)から、その4項目の合計を差し引いた値を表示しています。

「その他」という領域を設けた理由については、
SysStats Monitor/Liteの「その他」領域について - The iPhone Development Playground
を参照してください。

この状態から、ホームボタンのダブルクリックで「最近使ったアプリケーション」を表示させ、いくつかのアイコンを削除してプロセスを終了させると、それらのプロセスが確保していたメモリが解放されるはずです。実際には、以下のように表示が変化します。

さて、ここで大きく値が変動したのはどの部分でしょうか?
「その他」が減って、「空き」が増えているところが目立っていますね。それ以外の動きはごくわずかになっています。
このことから、iOSでは、アプリが確保しているメモリの多くは、vm_statistics構造体で見えているものとは異なる仕組みで管理されていることが推測されます。また、iPhone Simulatorでこのアプリを動かした場合は、その他に当たる部分は0になるので、iOS特有のことだと思われます。
同時にこれは、iOSでは、vm_statistics構造体で得られる値の中で、空きメモリサイズ以外の変化は、あまり意味が無いということも言えるかもしれません。iOS4になってからは、バックグラウンドプロセスの数が、かなり増えたので、以前よりもその傾向が強くなっているようにも思います。

このあたりは、開発者向けドキュメントでも説明されておらず、まだ謎に包まれたことが多いので、推測ばかりで何も結論が出ないモヤモヤした話になってしまいましたが、興味がある方にとって、何かのヒントになれば幸いです。

Out Of Memory Killerに似てるけど、もう少しやさしいiPhoneOSの空きメモリー制御

みなさんは、Linuxカーネルに備わっているOOM Killer(Out of Memory Killer)というものをご存知でしょうか?

OOM Killer(Out of Memory Killer)は,システムが実メモリーと仮想メモリー空間(スワップ領域)を使い切り,必要なメモリー領域を新たに確保できない場合に,プロセスを強制終了させて空きメモリーを確保する,Linuxカーネルの仕組みです。OOM Killerは,空きメモリーが確保できないことによりシステム自体が停止するという最悪の事態を避けるために用意されています。

Linuxキーワード - OOM Killer:ITpro

iOS4で、本格的なマルチタスク環境がサポートされるようになりましたが、3.x以前でも、Safariやメール、iPodなどの、標準アプリケーションは、バックグラウンド化されていたのは周知の通りです。
その頃から、iPhoneOSは、空きメモリが枯渇して来ると、バックグラウンドプロセスの中で、そのときに差し支えなさそうなものを選んで終了させるようなことが起きていたと思います。そのあたりの制御は、OOM Killerに似ていますね。

ただ、iPhoneOSは、有無を言わせずにいきなり強制終了するのではなく、いくつか段階を踏むことで、そのプロセスが必要な処理を行うためのチャンスをくれます。その代表的なものが、Memory warningであり、例えば、Safariの場合は、確保しておくことが必須でないメモリ領域を解放します。iOS4からは、一般のアプリもバックグラウンド化することができるようになったので、このMemory warningへの対処は、以前よりも重要になってきますね。

あと、ちょっと試してみたところでは、ホームボタンダブルクリックで表示される、「最近使ったアプリケーション」からアイコンを削除される場合も、単なる強制終了ではなく、-[UIApplicationDelegate applicationWillTerminate:]メソッドが呼ばれるようなので、ここでも必要な処理を行うチャンスは与えられているようです。
[7/9 追記] すみません。この部分は、誤った記述でした。さらに試してみたところ、-[UIApplicationDelegate applicationWillTerminate:]メソッドは、アプリがバックグラウンドに移ってもタスクを続行している場合は呼ばれましたが、suspend中は、killされてしまうという結果になりました。このあたり、まだまだ調査が必要ですね。

ただし、急激にメモリを獲得しようとするアプリが動いているときなど、本当に緊急を要する状況になった場合は、iOS4もOOM Killerのように、強制終了をするようなので、バックグラウンド化される前に、できるだけ安全な状態にしておくことは重要だと思います。当然のことながら、強制終了の場合は、それをされる側のアプリは、何もすることはできないので。。

マルチタスクに対応するアプリは、こういったことを、いろいろ考慮しなければならないので、もしもバックグラウンド化されることが必要ないのなら、非対応にしておくのが無難かもしれませんね。

このあたりの話題は、以下のところでも参考になる話題がまとめられていますので、ぜひ、目を通してみてください。
iOS4でマルチタスキングを実現するときの注意点まとめ - Togetterまとめ

私もまだ、調べ始めたばかりなので、いろいろ勉強しながら、マルチタスク対応アプリというものを考えて行きたいと思います。

iOS4でバックグラウンド化されているアプリを確認する方法

iOS4から提供されているマルチタスク機能によって、アプリ実行中にホームボタンを押した時に、プロセスを終了させず、バックグラウンドで一時停止状態にすることができるようになっていることは、多くの方がすでにご存知だと思います。
PCとは異なり、バックグラウンドで一時停止状態になるのは、すべてのアプリではなく、開発者がそう振舞うよう、選択したものだけです。また、OS4から提供されている機能なので、OS3.x以前のバージョンをベースにしたアプリでは、今まで通り、ホームボタンを押せば、プロセスが終了されます。
iOS4では、ホームボタンをダブルクリックすると、「最近使ったアプリケーション」が参照できるようになっています。ただそこでは、あくまでも「最近使ったアプリケーション」がわかるだけで、そのプロセスがバックグラウンドに存在するかどうかまではわかりません。

この例では、右の3つはバックグラウンドで一時停止状態になっているプロセスですが、一番左のものは、すでに終了されています。でも、その区別はつきませんよね。この機能を「タスクマネージャ」と表現している記事もあるようですが、その認識は誤っています。あくまでも、「最近使ったアプリケーション」のアイコンが並んでいるだけです。
バックグラウンドプロセスとして存在しているか否かは、SysStats MonitorSysStats Liteのようなアプリを使わないと、見ることができません。例えば、以下のようにプロセスの一覧を確認しながら、「最近使ったアプリケーション」を使って操作することができます。

「最近使ったアプリケーション」を長押しして、上記のような「-」を表示させた場合、アイコンを削除することができますが、そのアプリの状態よって、実際に行われることは異なります。

  • バックグラウンド化されているもの
    アイコンが削除されるとともに、プロセスが終了される
  • すでに終了しているもの
    アイコンが削除されるのみ

こんな感じで調べてみると、Appleから提供されている標準アプリは、ほぼバックグラウンド化する設定になっていることがわかりました。
そのときに調べてみた結果を以下にまとめてみましたので、興味がある方は、参考にしていただければと思います。

注意事項
SysStats MonitorSysStats Liteでは、OS3.x以前でバックグラウンド化されていたプロセスの名称は、事前に登録されていますが、それ以外のものは、「プロセスブックマーク機能」を使って1つずつ登録する必要があります。

SysStats Monitorのユニバーサル化を試してみました

iPadでは、従来の多くのiPhoneアプリを、そのまま何も手を加えずに動かすことできますが、iPad向けに作られたものと比べると、見劣りするというのが事実です。SysStats Monitorも、残念ながら、現時点ではその状態にあります。
iPadへの本格対応をする場合、当然のことながら、今のアプリをベースに作り込んで行くことになります。iPadが日本で発売されてから、すでに3週間経ってしまい、今さら感もありますが、まずは試しにアプリのプロジェクトをiPad対応(ユニバーサルバイナリ)にしてみたので、そのときのことを、メモを兼ねて、簡単にまとめてみました。

iPhone SDK3.2で実施したので、4.0には当てはまらない部分があることをご了承ください。

参考文献

当然ながら、iPad Programing GuideのStarting Your Project → Creating a Universal Applicationを参考にしました。 iOS Developer Library

ビルドターゲットをiPad用にアップグレード

iPad対応にするためのアップグレードは、ビルドターゲットごとに行うようになっている。まずは、プロジェクトの中の、ユニバーサル化したいターゲットを選択し、コンテキストメニューを開き、「現在のターゲットをiPad用にアップグレード...」を選択する。

以下のパネルが開くので、アップグレード方式を選択する。

ユニバーサル化の場合は、「One Universal application」が選択されていることを確認し、OKをクリック。

自動的に変更された項目

ここまでの作業で、自動的に変更されたのは、以下の項目でした。

ターゲットのプロパティ
  • Deployment

[変更前]

[変更後]

[変更前]

[変更後]

Info.plist

以下のように、Main nib file base name (iPad)というキーが追加される。
*1

グループの追加

Resources-iPadというグループが追加される。

とりあえず動かすためにやるべきこと

動く状態にするには、必要に応じて、以下のことを手作業で行います。

iPhone OS Deployment Targetの調整

iPhoneアプリケーションが3.1.3以前をベースとする場合は、iPhone OS Deployment Targetを、対応するバージョンに戻してやる必要がある。

この設定によって、アクティブSDKとして、3.1.3を選択できるようになる。
*2

iPad用のMain nibファイルを作成

Main nibファイルは自動生成されないので、自分で用意する必要がある。
今回のケースでは、元のiPhoneアプリのMain nibファイルをInterfaceBuilderで開き、「Create iPad Version」を選択することで、iPadサイズのWindowに変化されたnibファイルを生成した。

そのnibファイルを保存し、プロジェクトのResources-iPadの下に配置する。

アプリの実行

アクティブSDKを3.2に設定すればiPadアプリ、3.1.3に設定すればiPhoneアプリとして実行される。

実行してみた結果は。。

以下は、とりあえずiPadアプリとして実行したときのスナップショットです。

このような標準のUITableViewのみで構成されている画面は、自動的にリサイズされ、それなりに表示してくれます。
もう一つ見てみましょう。

こちらの画面は、元々ビューを固定された位置とサイズで配置しています。自動変換はされないので、iPadに最適なレイアウトになるよう調整することが必要です。

何となく感じがわかって来たので、遅ればせながら、iPad対応を本格的に検討してみようと思います。

*1:SDK3.2では、nibファイル自体は自動で生成されないので、自分で作る必要があります。

*2:SDK3.2では、これをやらないと、iPhoneアプリとしてiPhoneシミュレータで実行することができなくなってしまいます。