Hatena::ブログ(Diary)

かみやんの技術者日記 このページをアンテナに追加 RSSフィード

2010-06-23 [iPhone] 技術者向け iOS4.0の新機能

[] 技術者向け iOS 4.0の新機能 03:55  技術者向け iOS 4.0の新機能を含むブックマーク  技術者向け iOS 4.0の新機能のブックマークコメント


バックグラウンド実行

	UIDevice* device = [UIDevice currentDevice];
	BOOL backgroundSupported = NO;
	if ([device respondsToSelector:@selector(isMultitaskingSupported)])
	   backgroundSupported = device.multitaskingSupported;

バックグラウンドタスク初期化処理

  • バックグラウンドで長時間かかるタスクの場合
    • サスペンドする前いつでもアプリは、beginBackgroundTaskWithExpirationHandler:メソッドをコールしてシステムに長時間タスクがあることを伝えることができる。システムに受け入れられればバックグラウンドになるときにタスク処理を続けることができる。システムはすぐにサスペンドする代わりに追加の時間をアプリに与える。利用可能な残り時間は backgroundTimeRemainingプロパティで得られる。ユーザがアプリを終了させても突然終了させられることはない。例えばアプリが、重要なファイルをネットワークからダウンロード中であるとき、タスク初期化として下記のようなテクニックのデザインパターンがある。
      • この重要なタスクを beginBackgroundTaskWithExpirationHandler:とendBackgroundTask:でくくれば突然バックグラウンドになってもこのタスクは保護される。
      • applicationDidEnterBackground:イベントを待って、そこで1つまたは複数のタスクを開始する。
    • beginBackgroundTaskWithExpirationHander:をコールしたらその戻り値のIDで必ずendBackgroundTask:をコールしなければならない。endBackgroundTask:のコールはシステムにタスクが終了したことと、サスペンドしてもよいということを伝える。なぜならアプリバックグラウンドで実行可能な時間に限りがあるし、アプリの強制終了を避けるためにこのメソッドコールは重要である。アプリはいつでもbackgroundTimeRemainingプロパティで実行可能な残り時間を常にチェックできる。タスク処理中のアプリの残り時間がなくなったとき、サスペンドでなく強制終了させられる。アプリは、時間切れのハンドラをタスクごとに登録でき、そこでendBackgroundTask:をコールすることができる(ハンドラを登録しなかったり、登録してもそのハンドラでendBackgroundTask:をコールしなければ強制終了させられるということのようだ。逆にこの作法に従っていれば強制終了されられず、サスペンドになるようだ。ただしサスペンドに入れば通知なしでメモリ不足のときに破棄されるのでサスペンドが安全というわけではないが、時間切れハンドラでリソースの破棄や状態の保存をしておくことができる。時間切れハンドラがコールされて、endBackgroundTask:のコールまでに時間がかかる場合も強制終了になるようだ。

  あと残り時間というのは、既定で10分のようだ。

    • beginBackgroundTaskWithExpirationHandler: のコールで複数のタスクを発行でき、戻り値でユニークなIDが発行されるので、endBackgroundTask:のコールで対応したIDを渡す必要がある。
    • 下記はバックグラウンドに入るときに長時間タスクを開始するサンプル。bgTaskはメンバ変数とする。SDK4で追加されたブロックオブジェクトC言語の拡張文法、クロージャと思われる)を使ってシンプルに書いている。( dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),で、子スレッドの生成ができるようだ。dispatch_async( dispatch_get_main_queue() で、メインスレッドに移ってendBackgroundTask:をコールしているようだ)
	- (void)applicationDidEnterBackground:(UIApplication *)application
	{
	    UIApplication*    app = [UIApplication sharedApplication];
 
	    // Request permission to run in the background. Provide an
	    // expiration handler in case the task runs long.
	    NSAssert(bgTask == UIBackgroundTaskInvalid, nil);
 
	    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
	        // Synchronize the cleanup call on the main thread in case
	        // the task actually finishes at around the same time.
	        dispatch_async(dispatch_get_main_queue(), ^{
	            if (bgTask != UIBackgroundTaskInvalid)
	            {
	                [app endBackgroundTask:bgTask];
	                bgTask = UIBackgroundTaskInvalid;
	            }
	        });
	    }];
 
	    // Start the long-running task and return immediately.
	    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
 
	        // Do the work associated with the task.
 
	        // Synchronize the cleanup call on the main thread in case
	        // the expiration handler is fired at the same time.
	        dispatch_async(dispatch_get_main_queue(), ^{
	            if (bgTask != UIBackgroundTaskInvalid)
	            {
	                [app endBackgroundTask:bgTask];
	                bgTask = UIBackgroundTaskInvalid;
	            }
	        });
	    });
	}
  • ローカル通知の配信スケジュール
    • UILocalNotificationクラスはローカル通知の配信のスケジュールの仕方を提供する。プッシュ通知と違って、リモートサーバを立てる必要がない。ローカル通知はアプリがスケジュールをして、そのデバイスで実行される。アプリは下記のような実行をすることができる。
      • タイムベースアプリは、未来の指定した時間にアラートを表示するようにシステムに依頼できる。例えばアラームアプリなど。
      • バックグラウンドで実行しているがユーザの興味を引くためにローカル通知を投げることができる。
    • ローカル通知をスケジュールするためには、UILocalNotificationのインスタンスを生成し、それを設定し、UIApplicationのメソッドを使ってスケジュールする。ローカル通知はサウンド、アラート、バッチの通知タイプと必要なら時間の情報を含める。UIApplicationクラスは通知をスケジュールした時刻に配布するか、即配布するかのオプションを提供する。
    • 次の例では1つのアラームをスケジュールしている。このサンプルでは新しいアラームの設定のために前のスケジュールをキャンセルして設定していて1つのアラームしか対応していない。アプリケーションは指定した間隔でリピートするスケジュールも含め、最大128スケジュールまで持つことができる。アラームが発行するときアプリケーションが実行されていなくてもバックグラウンドであるときでもサウンドとアラートを再生する。アプリケーションフォアグラウンドで実行しているときは、application:didReceiveLocalNotification:メソッドが呼ばれる。
	- (void)scheduleAlarmForDate:(NSDate*)theDate
	{
	    UIApplication* app = [UIApplication sharedApplication];
	    NSArray*    oldNotifications = [app scheduledLocalNotifications];
 
	    // Clear out the old notification before scheduling a new one.
	    if ([oldNotifications count] > 0)
	        [app cancelAllLocalNotifications];
 
	    // Create a new notification
	    UILocalNotification* alarm = [[[UILocalNotification alloc] init] autorelease];
	    if (alarm)
	    {
	        alarm.fireDate = theDate;
	        alarm.timeZone = [NSTimeZone defaultTimeZone];
	        alarm.repeatInterval = 0;
	        alarm.soundName = @"alarmsound.caf";
	        alarm.alertBody = @"Time to wake up!";
 
	        [app scheduleLocalNotification:alarm];
	    }
	}
    • ローカル通知のサウンドの要件はプッシュ通知と同じ。カスタムサウンドはアプリのメインバンドルの中におく必要がある。サポートしているフォーマットは、リニアPCMまたはMA4, uLaw, aLawである。soundNameを@"default"にするとシステムのデフォルトサウンドが鳴る。サウンドを鳴らすとき、デバイスの設定によってはバイブレーションがなる。
  • バックグラウンドでロケーションイベントの受信
    • アプリバックグラウンド中でもユーザの位置を追跡することができる。次の3つの方法でロケーションイベントを受け取ることができる
      • シグニフィカント位置変更(推奨):シグニフィカント位置変更サービスは、携帯電話網を使ったOS4以降で動作する。これは電力の消費が小さいので、これを使うことを強く推奨する。ユーザの位置が大きく変わったときのみ新しい位置に更新される。このサービスをバックグラウンドで実行中にアプリサスペンドしていたときは、イベントをハンドルさせるためにシステムはアプリを起こす。同様にこのサービスを実行しているときにアプリが終了していた場合は、新しい位置データが来たときに、自動的にアプリを起動する。
      • 標準ロケーションサービス:このサービスはすべてのOSのバージョンでアプリフォアグラウンドでもバックグラウンドでも継続的な位置の更新を提供する。しかし、アプリサスペンドしていたり、終了していたりしたとき、新しい位置のイベントは、アプリを起こしたり、起動したりしない。(この方法は今までもあった。ただバックグラウンドで実行というコンテキストがなかっただけ。長時間タスク宣言によるバックグランド実行と組み合わせれば、一定時間はバックグラウンドで位置が取得しつづけられるよ。ということだろう)
      • 宣言付きのロケーションアプリ:標準ロケーションサービスで継続的に位置を受け取るアプリは、継続的に位置情報を受け取るバックグラウンドアプリとして自ら宣言しなければならない。Info.plistにUIBackgroundModesキーを含め、それにlocationという文字を配列に入れた値をセットする必要がある。このキーをセットしたアプリがロケーションサービスを実行中に、バックグラウンドになるときは、サスペンドすることなくバックグラウンドでロケーション関連のタスクを続けることができる。(ロケーションサービスを使っていなくてもよいのか?)
    • いずれの方法でも、無線電波ハードウェアがあり、ONになっている必要がある。バッテリー消費の観点から、高精度な位置が不要ならシグニフィカント位置変更を使うべきだ。このサービスを開始するには、CLLocationManagerのstartMonitoringSignificantLocationChangesメソッドをコールする。このサービスは大きな位置変更のときのみ通知し、無線ハードウェアの電力を落とすことができる。例えば、ロケーションマネージャは、新しい無線電波等を発見して通信するときまで何もしないだろう。
    • もうひとつシグニフィカント位置変更のメリットとしてアプリサスペンドから起こしたり、終了していれば起動してくれることである。もしアプリサスペンドしているときにイベントが来たらアプリは起こしてもらえて、イベントをハンドルする少しの時間が与えられる。もしアプリが終了しているときにイベントが来たら、application:didFinishLaunchingWithOptions:メソッドのオプションに UIApplicationLaunchOptionsLocationKeyキーがセットされてくる。アプリは、そこでロケーションサービスを再び開始して新しい位置をえればよい。
    • ナビゲーションアプリのように定期的な間隔でより精度の高い位置あ必要なアプリは、宣言付きバックグランドアプリにする必要がある。この選択はバッテリーの消費を高めることに留意しなければならない。可能ならばこの選択は避けるべきである。
    • シグニフィカント位置変更や標準ロケーションサービスでは、イベントが発生すると、locationManager:didUpdateToLocation:fromLocation:デリゲートメソッドが呼ばれる。このメソッドでは、バックグラウンドでやらなければならないことだけを処理するように実装しなければならない。例えば、ビューの移動などはフォアグラウンドの場合のみすべきである。
  • VoIPアプリの実装
    • VoIP(Voice over IP)はデバイス携帯電話網を使わずインターネット接続を使って電話をすることができるアプリである。このようなアプリは対応するサービスに永続的に接続を続ける管理が必要である。この永続的な接続によって着信を受けて応答ができる。システムは、アプリサスペンド中でもその接続を管理する機能を提供する。
    • VoIPアプリを設定するため、下記の手順を行う必要がある
      • Info.plistにUIBackgroundModesキーを追加し、値にvoipという文字を追加した配列を設定する
      • VoIPに使うソケットを設定する
      • バックグラウンドへ移行するときに、setKeepAliveTimeout:handler:メソッドをコールして、自分のサービスを管理するために起きなければならない頻度を設定する。
    • voipの文字列をUIBackgroundModesキーに設定することでシステムはネットワークソケットを管理しなければならないことを知る。VoIPサービスがいつでも動作できるようにシステムがブートした直後もアプリバックグラウンドで起動する。大抵のVoIPアプリは、UIBackground Modesキーにaudioもたす必要があるだろう。
    • VoIPとしてソケットを使う場合に、設定をする必要がある。
      • NSInputStream、NSOutputStreamの場合:setProperty:forKey:メソッドで、キーに NSStreamNetworkServiceType、値にNSStreamNetworkServiceTypeVoIP
      • NSURLRequestの場合: setNetworkServiceType:メソッドで、値として、NSURLNetworkServiceTypeVoIP
      • CFReadStreamRef、CFWriteStreamRefの場合:CFReadStreamSetPropertyやCFWriteStreamSetProperty関数で、キーにkCFStreamNetworkServiceType、値にkCFStreamNetworkServiceTypeVoIP
    • このソケットに対する設定は、信号用のソケットにする必要がある。音声用のソケットには設定しない。
    • もしアプリサスペンド中にこの信号用ソケットにデータが来たときは、データを処理するためにシステムはアプリを起こす。この場合、着信があったらアプリはすぐにローカル通知を使ってユーザに知らせるだろう。他の重要でないデータのときは、システムは、アプリがデータを処理し終わったらアプリサスペンドに戻すだろう。
    • 着信を受けるためにVoIPアプリは起動されていなければならないため、もしアプリがEXITコード0以外で終了したときは、システムは自動的にアプリ再起動する(メモリ不足で落ちたときも0以外となると思われる)。しかし、システムは接続を管理しないのでアプリ再起動時に自分で最初から接続をしなおす必要がある。
    • KeepAliveハンドラの登録
      • VoIPアプリは起動していることをサービスに通知するために、定期的に信号を送る必要があるだろう。アプリバックグラウンドに移行するときにsetKeepAliveTimeout:handler:メソッドでハンドラを登録する。ハンドラが呼ばれたらできるだけ速やかに処理を終えること。最大でも30秒までである。30秒を超えた場合は、システムはアプリを終了させる。
      • ハンドラを登録するときに、インターバルを設定できるが、これはサービスが許す最大の時間を設定すべきである。600秒より短いインターバルはシステムは受け付けない。システムはそのインターバルより前にハンドラをコールする。
Connection: close