先日、SDK互換性に関する内容と、>iOS 4.0 マルチタスク対応に関する資料をまとめたところで、今回は、実際にマルチタスク対応をするにあたり注意すべき点のまとめ。


なお、ここではバックグラウンド実行は行わないものとする。バックグラウンドではただひたすらサスペンドしているだけでも、OS4.0でビルドをする場合、以下の内容を考慮しなくてはならない。



はじめに:状態遷移の確認

マルチタスク化に伴い、アプリケーションの状態は以下の5通りとなった。

Not running 起動していない。
Inactive Foregroundで起動済みだがイベントを受付けていない状態。例えばSMSなどを受信してシステムがメッセージを表示しているときなど。
Active Foregroundで起動済み、かつ、イベント受付け中。通常の動作状態。
Background Backgroundにて実行中。一般的にはこのステータスになる時間は極めて短いが、必要に応じてさらなるBackgroundでの実行をシステムに要求することができる。(マルチタスク対応デバイスのみ)
Suspended Backgroundにて停止中。BackgroundステータスからSuspendedステータスへはシステムによって自動的に移行する。本ステータスにいる際にメモリ不足となった場合は、システムは予告なしにアプリケーションを終了する。(マルチタスク対応デバイスのみ)


最後の二つが今回から追加された状態であり、状態の変化はNotificationで取得することが可能。また、同時にUIApplicationDelegateのメソッドもコールされる。

状態遷移やそのときにポストされる通知については iPhone Application Programming Guide が詳しいのでそちらを参照のこと。

これらの状態を踏まえて、マルチタスク化をする際に”最低限”やっておくことを順にまとめる。



※なお、どうしても以下の処理ができないのであれば、必ずinfo.plistにUIApplicationExitsOnSuspendを追加して値をYESにしておこう。

終了処理

終了時に通るパス

アプリケーションが”Active”の状態から”Not running”の状態に遷移する過程で、必ず通るパスがある。そこでデータや設定などを保存しているアプリが多いと思うのだが、この「必ず通るパス」が以下のように変更になっている。気がつかなかったでは済まされない変更だ。

マルチタスク未対応OS : applicationWillTerminate
マルチタスク対応OS : applicationDidEnterBackground

OS3.xでは、ユーザーがホームボタンを押したときにアプリの状態が”Active”から”Not running”へ移る。この過程で呼ばれるデリゲートメソッドが、applicationWillTerminate:だ。

一方、OS4.0以降(かつマルチタスク対応デバイス)では、ユーザーがホームボタンを押した際にアプリの状態は”Active”から”Backtrounded”を経て”Suspended”へ移行する。この段階で呼ばれるデリゲートメソッドが、applicationDidEnterBackground:である。

では、いつ”Not running”に移行するのか。ひとつは、”Suspend”の状態でユーザーが「最近使ったアプリケーション」(ホームボタンダブルクリックで表示されるアイコンリスト)からアプリを削除した場合。もうひとつは、別のアプリが実行中にシステムのメモリが不足してきた場合だ。メモリ不足の際には、確保しているメモリが多いアプリほど削除されやすいらしい(システムが勝手にやることなので自分でコントロールすることは不可)。

このときアプリには何か通知がくるのか?答えはNOだ。冷静に考えればアプリはSuspendしているのだから通知など送られても受け取れない。

つまり、OS4.0以降(かつマルチタスク対応デバイス)では、“Active”から”Not running”へ移行する際に「必ず通るパス」がapplicationDidEnterBackgroundである。

これまでapplicationWillTerminateでデータや設定の保存をしていた場合、applicationDidEnterBackgroundでも同じ処理をする必要がある(該当する通知はUIApplicationDidEnterBackgroundNotification)。

マルチタスク未対応デバイスのケア

さらに、ここでひとつ注意が必要。

OS4.0だからといって、必ずしもマルチタスクに対応しているわけではない。さらに言うと、Deployment TargetをOS3.xにしている場合は該当する通知すら存在しない。

マルチタスク対応ではないOS上での動作は、これまで通り”Active”から直接”Not running”へ移行する。このとき通るパスも従来通りapplicationWillTerminateだ。

したがって、applicationWillTerminate, applicationDidEnterBackgroundの両方ともで適切な処理をする必要がある。

OS3.xのケア

もうひとつ、通知(NSNotification)を登録している場合は、OS3.xへの対応時にさらなる注意が必要となる(Deployment OS versionを3.xにした場合)。

OS4.0から追加になったいくつかの通知はOS3.xには存在しない。したがって、そのまま使っているとOS3.x上で起動した場合にはクラッシュなどの問題を引き起こす(Base SDKは4.0なのでコンパイル時にはエラーとならない)。

そのため、OS3.xもサポートしていく場合には、通知を利用する際に以下のようなチェックが必要となる。

if (&UIApplicationDidEnterBackgroundNotification) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:[UIApplication sharedApplication]];
}

ここで、通知名がUIApplicationDidEnterBackgroundNotification、これは文字列定数(const宣言されている)である。文字列定数の有無を調べるために、そのアドレスをチェックしている。

これで、OS3.x上で動作した場合には、条件文ではじかれるため以下の処理は走らない。

なお、文字列定数のチェックに関しては、フォーラムにいくつか議論があるので不安なときはそちらも参照するとよいかもしれない。

バックグラウンド/フォアグラウンド遷移時の処理(マルチタスク対応OS/デバイスのみ)

マルチタスク対応OS/デバイス上で動作している場合、これまでとは異なり、アプリケーションのライフサイクル内で様々なことが起こりうる。例えば設定の変更など。これまでは、設定を変更するためには、必ず一度アプリを終了し設定アプリを開く必要があった。しかし、マルチタスク化された今、アプリがsuspendした状態で設定が変更されてしまう。

設定データの読み込みを、起動時のみに行っていたアプリは要注意である。他にもいくつか注意する点があるので、簡単にまとめておく。

設定データ

NSUserDefaultsを利用して設定データを管理している場合、アプリが再び”Active”になる段階で通知が挙げられる。通知名はNSUserDefaultsDidChangeNotification。この通知を登録しておき、必要な処理を行う。


なお、”Suspended”から”Active”に戻って来る際に、viewは再描画されない。あくまでも、suspendしていたアプリがresumeするだけだと考えたほうがよい。したがって、設定内容によって表示状態が異なる場合には、該当するViewの再描画が必要となる。

地域情報

こちらも設定同様、アプリがsuspendしている間に変更される可能性がある。地域情報を用いて文字列(数字や日付など)を表示しているような場合には、必要に応じて再描画が必要だ。suspend中にcurrentLocaleが変更になった場合はNSCurrentLocaleDidChangeNotificationで通知される。

なお、autoupdatingCurrentLocaleクラスメソッドを用いて作成されたNSLocaleオブジェクトは自動で更新されるため、再生成の必要はない。

また、NSFormatterオブジェクトは、基本的にキャッシュせず(オブジェクトを保持せず)必要になるたびに生成すべきだとされている。


注意:iPhone Application Programming Guideには言語変更時にも通知が来ると書かれているが、実際に言語を変更してみるとアプリがKILLされる模様。ローカライズしているかどうかも関係しているかもしれないが、要調査。KILLされているうちはよいが、ある日突然KILLされずに通知があがるようになるかも知れないので油断大敵。

その他の状態変化

その他、デバイスの回転やバッテリーレベル、アクセサリーの接続状況など、重要な状態変化はNSNotificationで通知されるため、必要に応じて通知の登録をし処理を追加しておく。
詳細はこちらを参照のこと。Responding to System Changes While in the Background

アラート表示

UIAlertViewを用いてアラート表示している段階でバックグラウンドへ移行すると、そのアラートは残ったままとなる。再び”Active”状態になったときも変わらず表示されている。

ここで注意が必要なのは、suspend中に状況が変化し、アラート表示が不要になる可能性があるということ。また、その後の処理も変化してくる可能性がある。したがって、アラート表示はバックグラウンドに入る際に消しておくべきである(アップルの公式ドキュメントでは、状態や処理に変化がなくても消しておくことが推奨されている)。

アラートを消すために用いるメソッドは、

- (void)dismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animated;

だ。どのボタンを押したことにするかは状況に次第だが、キャンセルボタンを押した状態にすればよいのであれば、buttonIndexにalertView.cancelButtonIndexを渡せばいい。

そして、この処理をバックグラウンドに入った際に行えばよいので、UIApplicationDidEnterBackgroundNotificationに登録したセレクタで以下のような処理をすればよい。

- (void) applicationDidEnterBackground:(NSNotification *)notification {
if (self.alertView != nil) {
[self.alertView dismissWithClickedButtonIndex:self.alertView.cancelButtonIndex animated:NO];
}
}

キーボード

こちらもケアしておいたほうがよいだろう。キーボードの種類なども変更される可能性があるので、バックグラウンドに入る前にキーボードを消しておくべきだと思われる。

なお、キーボードを表示したままでsuspendし、表示中のキーボードを設定で未選択にした場合、再表示時には自動で切り替わるようだ(例えば、日本語キーボードを表示中にsuspend、設定で英語キーボードのみにして、再度active状態に入ると、自動的に英語キーボードに切り替わる)。

ただし、これは経験上のことなので、例外の有無はわからない。やはり念のためキーボードは消しておいたほうがよいと思う。

メール設定

MFMailComposeViewControllerを使ってアプリ内でメールの送信を行っている場合、suspend中にメール設定が削除される可能性を考慮した設計が必要となる。

例えば、メール作成画面を表示するまでのステップとして以下のようにしているとしよう。

MFMailComposeViewController *mailViewController = [[MFMailComposeViewController alloc] init];
mailViewController.mailComposeDelegate = self;
// setup the title, body, attached file etc of the mail
[self presentModalViewController:mailViewController animated:YES];  // mailViewController must not be nil!!! 
[mailViewController release]; 

MFMailComposeViewContrllerは、メール設定が完了していない場合initでnilを返す(上記の場合、mailViewControllerがnilになっている)。nilであることに気がつかず、そのままその先のpresentModalViewControllerに渡すとクラッシュするので要注意。メール設定の有無は、canSendMailクラスメソッドで確認できるので適所で確認が必要である。

マルチタスクOS対応前に入れていたcanSendMailの位置が適切かどうか、今一度確認しておきたいところ。

なお、一度MFMailComposeViewControllerへ処理が渡ってしまうと(メール作成画面を表示している状態)自分のアプリではどうすることもできない。

注意:メール設定を削除した直後にwillEnterForegroundやdidBecomeActiveでcanSendMailするとYESが返ってくることがある。詳細は確認中だが注意が必要。MFMailComposeViewControllerを作る直前でしつこくチェックしたほうがよい。

以上、ざっと挙げただけでもケアすべき点は結構あります。さらに、OSやデバイスの種類ごとにテストをするとなると、気が遠くなりそうなわけですが、開発者として、マルチタスクOSへの対応は避けては通れないものですね。。。開発者のみなさま、がんばりましょう。

間違いなどありましたら指摘していただけると助かります。その他、質問などなどはツイッター(@natsun_happy)で。

関連記事


   
© 2012 iOS 開発ブログ Natsu's note Suffusion theme by Sayontan Sinha