iOS4以降、iOSアプリを作る場合はマルチタスク(Fast App Switching)の対応を考慮しなければいけなくなりました。バックグラウンドに入ったらアプリを終了するようにして、マルチタスクをサポートしないようにすることもできますが、AppStoreでリリースされている多くのアプリがマルチタスクをサポートしており、ユーザがマルチタスクに慣れていることを考えると、できるだけマルチタスクをサポートしておきたいものです。
Fast App SwitchingのハンドリングはAppDelegateで行っている人も多くいると思いますが、コードの再利用性を考えるとNotificationCenterを使った方が効率的な場合があります。
アプリをFast App Switchingに対応させるには、以下の実装が必要です。
- アプリがバックグラウンドに切り替わった時の対応(ゲームをポーズ状態にする、BGMをとめる、時計を止める、等)
- アプリがフォアグラウンドに切り替わった時の対応(BGMを再開する、等)
アプリによって、具体的な処理の内容は変わりますが、「バックグラウンドに入ったタイミング」「フォアグラウンドに戻ったタイミング」で適切な処理をしてあげる、のが基本的な対応方法になります。
Fast App Switchingについて説明されている本やウェブでは、AppDelegateのデリゲートメソッドを使った対応方法がよく扱われています。
- (void)applicationDidEnterBackground:(UIApplication *)application { // ここでBGMを止めたり、ゲームをポーズ状態にしたりする } - (void)applicationWillEnterForeground:(UIApplication *)application { // ここでBGMを再開したりする }
このデリゲートメソッドを使う方法は、通知が一カ所に来るという意味ではわかりやすいのですが、実際に使っていると不便な点があります。
- 普通AppDelegateにはあまり具体的な実装はしないので、AppDelegateのメソッドの中から、実際に処理(BGMをとめたり、ポーズ状態にする)をするオブジェクトに通知をしなければいけない。そのときに不自然な参照が発生したり、コードが冗長になりやすい。
- アプリのそのときの状況(ステージをプレイ中なのか、メニュー画面なのか、ヘルプ画面なのか)によって、処理したい内容が変わってくる場合のハンドリングが面倒。
- これらのイベントに対応しなければいけないオブジェクトの再利用時のコストがあがる(そのオブジェクトを使うアプリは、それぞれのアプリケーションのAppDelegateで、毎回そのオブジェクトにイベントを通知するコードを書かなければ行けなくなる)。また、それに伴うトラブル発生の可能性が増える(イベントを通知し忘れる、等)。
特に後者は生産性に直結する問題ですが、幸いCocoaにはNotificationCenter(iOS5のUIのことではなく、NSNotificationCenterの方。紛らわしい…)というオブジェクト間の関係を疎結合な状態にしつつ、オブジェクト間の通信を可能にする素敵な仕組みがあり、Fast App Switchingに関するイベントもこれを使ってハンドリングできます。前回の記事でも使いましたが、コードはシンプルです。
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { // ここでBGMを止めたり、ゲームをポーズ状態にしたりする } ]; [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { // ここでBGMを再開したりする } ];
このような形で実装しておけば、オブジェクトを別のプログラムで再利用するような場合でもいちいちAppDelegateのデリゲートメソッドからイベントを手動で通知する必要がなくなります。
また、ViewControllerの場合は、
@property (strong) NSMutableSet* appSwitchObservers; ---- - (void)viewDidAppear { self.appSwitchObservers = [NSMutableSet new]; // この画面が表示されている間にアプリが切り替わったら下記の処理が走る [self.appSwitchObservers addObject:[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { // ここでBGMを止めたり、ゲームをポーズ状態にしたりする } ]]; [self.appSwitchObservers addObject:[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { // ここでBGMを再開したりする } ]]; } - (void)viewWillDisappear { // 画面が消えたら、この画面用のアプリきりかえ時のコードも走らないようにする for (id observer in self.appSwitchObservers) [[NSNotificationCenter defaultCenter] removeObserver:observer]; self.appSwitchObservers = nil; }
のように書くと、画面毎のアプリ切り替え対応をスマートに実装できます。
シンプルなアプリケーションで、コードの再利用等がない場合はわざわざNotificationCenterを使わずにAppDelegateを使った実装でも問題ありませんが、applicationDidEnterBackground:内のコードが長くなって読みにくくなってしまっているような場合はNotificationCenterを使った実装を検討してみましょう。
“NotificationCenterを使ったFast App Switching対応 | なんてこったいブログ” http://t.co/ky5tB8nJas