かみやんの技術者ブログ

主にプログラムの話です

技術者向け iOS 4.0の新機能

  • OS4.0はiPadには対応していない。iPhoneiPod touchのみに対応している。
  • マルチタスク
    • SDK4.0以降でビルドし、OS4.0以降で実行すればアプリケーションはホームボタンを押しても終了しない。
    • ホームボタンを押すと、バックグラウンド実行コンテキストへ移る。これは多くのアプリではバックグラウンドになったらすぐにサスペンド状態に入るということ。アプリをメモリに保持し起動処理を省略することができ、ユーザエクスペリエンスは、向上する。また、アプリをサスペンドすることで、CPUパワーの使用は最小化され、フォアグランドアプリの実行時間を増やす。
    • 大抵のアプリではバックグラウンドになったらすぐにサスペンドする。バックグラウンドでも動き続けるアプリの場合は、次のテクニックが必要である。
      • アプリは、いくつかの重要なタスクを完了するために有限の時間を要求できる(長時間タスク)
      • アプリは、定期的なバックグラウンド実行を要求する特定のサービスをサポートすることを宣言できる(audio location VoIP)
      • アプリは、アプリが実行中であるかないかにかかわらず、指定した時刻にアラートを生成する、ローカル通知を使うことができる。
    • バックグラウンドアプリケーションはメモリ不足など特定の条件で終了させられるので、それに即時対応しなければならない。アプリケーションの実行状態が変わるときにアプリケーションデリゲートで通知されるので、それを実装しなければならない。
  • ローカル通知
    • ローカル通知は、外部のサーバと連携して通知を生成するプッシュ通知の代わりにローカルで通知を生成でき、プッシュ通知の補完をする。バックグラウンドアプリケーションは、重要なイベントが起きたときにユーザに通知をするためにローカル通知を使うことができる。たとえばバックグラウンドで動作するナビゲーションアプリであれば、曲がり角についたときにユーザに通知することができる。アプリはローカル通知の配達時刻を未来に予約することもできる。これはそのときアプリケーションが動作していなくてもよい。ローカル通知の利点としてアプリケーションと独立であり、アプリがローカル通知をシステムに予約したらアプリが動作していなくてもシステムが管理してくれることである。
  • EventKit
    • EventKitフレームワークは、ユーザのデバイスのカレンダーにアクセスするインターフェースを提供する。これを使うとユーザのイベントを取得したり、新しいイベントを追加できる。カレンダーイベントはアラームを含めることができる。EventKitのビューコントローラを使うと標準的なUIでイベントを閲覧したり編集することができる。
  • データ保護
    • データ保護は、ファイルの暗号化を行う。デバイスがロックされているときは、暗号化され、ユーザがアンロックすると複合化される。データ保護を使う場合は、ファイルの生成時とユーザがロックするときアンロックするときにちょっとした準備が必要である。
    • ロック、アンロックはパスコードロックのことであろう。
  • Core Telephony
    • Core Telephonyフレームワークは携帯電話の無線機能を持つデバイスで、電話ベースの対話をするインターフェースを提供する。このフレームワークを使うことでユーザの携帯電話サービスプロバイダの情報を得ることができる。携帯電話の呼び出しに興味あるアプリは、呼び出しがあったときに通知を得ることができる。
  • iAd
    • iAdフレームワークを使うことでバナーベースの広告をアプリで配信することができる。広告はアプリのユーザインターフェースに埋め込むことができ、好きなときに表示できる。このビューは広告のロードや表示やタップしたあとの応答も含めすべて自動でAppleの広告サービスと連携して動作する。
  • クイックルック
    • クイックルックフレームワークは、あなたのアプリが直接対応していないファイルでもプレビューを直接表示するインターフェースを提供する。このフレームワークは、ネットワークからダウンロードしたファイルや、別のところから得たファイルを表示する。このフレームワークで提供されるビューコントローラでユーザインターフェースに埋め込んで表示できる。
  • AV Foundation
    • OS3.0からあるが、強化された。興味ないので詳細省略(メディアアセット管理、メディアの編集、動画のキャプチャ、動画の再生トラックの管理、メディアアイテムのメタデータの管理、ステレオサウンドのパン、サウンド同士の同期など)
  • Asset Library
    • Asset Libraryフレームワークは、ユーザの写真やビデオのクエリベースのインターフェースを提供する。
  • ImageI/O
  • Core Media
    • Core Mediaフレームワークは、低レベルのメディアタイプを提供する。普通の人は使わないがより詳細な生成や再生を必要とする人が使う
  • Core Video
  • ブロックオブジェクト
  • GCD(Grand Central Dispach)
    • BSDオープンソースの技術で、マルチスレッド(マルチコアCPU用と思われる)用の非同期ライブラリ。C言語では有名なのかな?
  • Accelerateフレームワーク
  • Xcode
    • Xcode 3.2.3ではオーガナイザーウインドウで自動プロビジョニングプロファイルがついた。プログラムポータルでデバイスをチームポータルに登録してプロビジョニングプロファイルをダウンロードしなくてよくなった。
    • アプリ内課金とプッシュ通知のアプリは引き続きチームポータルへの登録が必要。だが、一度登録すればあとは自動
  • UI Automation
    • InstrumentsでJavaScriptによるテストができるようになった(タップとかフリックとかをJavaScriptで書くのであろう)
  • Foundation
    • UILocalNotificationクラス追加
    • NSRegularExpression、NSDataDetector(文字コード認識か?)、NSTextCheckingResult (スペルチェックか?)
    • NSFileManagerクラスにデータ保護機能追加
    • NSFileWrapperクラスは、パッケージベースドキュメントタイプに対応(?なんのこと?)
    • NSOrthographyクラスは、スペルチェックや文法チェックかな?
  • OpenGL ES関連(興味ないので省略)
  • GameKit関連(興味ないので省略)
    • ユーザ名の別名とか、リーダーボード(ハイスコアランキング)とか、マッチメーキングとか。
  • Core Location(興味ないので省略)
    • バッテリ使用量の削減とか。シグニフィカント位置情報かな?
  • MapKit(興味ないので省略)
  • MessageUI
    • SMS(ショートメッセージ)も作成できるようになった
  • Core Graphics
    • PDFのメタデータの変更
    • ICCプロファイルを使ったカラースペースの生成
    • フォントのスムージング
  • ICU
    • ICUのバージョンが4.4になった。相変わらず正規表現部分のヘッダファイルのみ公開。(OS3.2ではICU4.2であった)
    • iPhoneiPod touchではOS3.2は動作しないが、OS3.2で追加された機能もiPhoneiPod touchで利用できるようになる(スプリットビュー以外と思われる、ポップオーバーは使えるっぽい)

バックグラウンド実行

	UIDevice* device = [UIDevice currentDevice];
	BOOL backgroundSupported = NO;
	if ([device respondsToSelector:@selector(isMultitaskingSupported)])
	   backgroundSupported = device.multitaskingSupported;
  • アプリがバックグラウンドタスクに対応していることを宣言する
    • Info.plistにUIBackgroundModesキーを追加し、次の値を列挙する。
    • audio location voip。audioは、音楽の再生。locationはGPSデータの取得。voipは、インターネット接続での会話(Skypeなど)
    • audioであれば、システムは、オーディオバッファにデータを埋めるに必要なCPU時間を定期的にくれる。
    • アプリは与えられたタスクを完了するためにシステムに空き時間を問い合わせることができる
    • アプリは事前に決めた時間にローカル通知が配信されるように予約することができる。
    • バックグラウンド状態の変化のサポート
      • アプリデリゲートに次の通知が追加された
        • application:didFinishLaunchingWithOptions:
        • applicationDidBecomeActive:
        • applicationWillResignActive:
        • applicationDidEnterBackground:
        • applicationWillEnterForeground:
        • applicationWillTerminate:
  • バックグラウンドでOpenGLESコンテキストを作ってはならない
  • サスペンドに入るときにBonjourは中止すること
  • ネットワークベースソケットの切断をハンドルすること
  • バックグラウンドに入る時にアプリの状態をストレージに保存すること
    • メモリが少なくなってきたときに、バックグラウンドアプリは通知なしに破棄される
    • サスペンドアプリが先に破棄され、それでも不足のときはバックグラウンドアプリが破棄される
  • バックグラウンドに入る時に必要のないメモリは開放すること
  • サスペンドに入る前に共有システムリソースの利用は停止すること
    • アドレス帳のような共有システムリソースをサスペンド前に利用停止する必要がある。それはフォアグラウンドアプリケーションが使うためである。もし使用中のままアプリがサスペンドした場合は、強制終了させられる。
  • バックグラウンド中は、ウインドウやビューを更新しないこと。しても強制終了はさせられないけどCPU時間の無駄である。
  • バックグラウンドに入るときに、システムがスクリーンショットを取るため、パスワードなどの取り扱い注意の情報をビューに描画しておかないこと
  • バックグラウンド中にCPUパワーの使用は最小限にすること

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

  • バックグラウンドで長時間かかるタスクの場合
    • サスペンドする前いつでもアプリは、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:デリゲートメソッドが呼ばれる。このメソッドでは、バックグラウンドでやらなければならないことだけを処理するように実装しなければならない。例えば、ビューの移動などはフォアグラウンドの場合のみすべきである。
  • バックグラウンドオーディオの再生
    • Info.plistにUIBackgroundModesキーを追加して、値にaudioという文字を追加した配列を設定する
    • それ以外は興味がないので省略。特にAPIはなさそう。オーディオ再生中にバックグラウンドに入ったらシステムが再生を続けてくれるのかな。
  • 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秒より短いインターバルはシステムは受け付けない。システムはそのインターバルより前にハンドラをコールする。