ラベル Objective-C の投稿を表示しています。 すべての投稿を表示
ラベル Objective-C の投稿を表示しています。 すべての投稿を表示

2013/12/02

iOS の task completion について

TMPTaskCompletionManagerの公開に伴い、task completionについてまとめてみます。

task completion は iOS4 で導入されたマルチタスク処理方式の一つです。アプリがバックグラウンドモードへ入った後も、実行中の処理を継続したいときに使います。実行可能時間は、iOS6までは最長10分間。iOS7では最長3分間へ短縮されました。

バックグラウンドでの実行要求は、
- [UIApplication beginBackgroundTaskWithExpirationHandler:]
で行います。要求は何度でも可能です。
処理が終了したなど、バックグラウンドでの実行が不要になった場合には、
- [UIApplication endBackgroundTask:]
を呼び出さなければなりません。このメソッドを呼び出すか、規定時間が経過した場合、アプリケーションはバックグラウンド処理を終了し、サスペンド状態へ入ります。

StackOverflowでも言及されているように、beginBackgroundTaskWithExpirationHandler:は、名前に「begin」と付いているものの、実際にはバックグラウンド処理を開始しません。このメソッドは、OSに対し、バックグラウンドでの処理継続を要求するために使います。

beginBackgroundTaskWithExpirationHandler:が呼び出される毎に、OSはその回数を記録します(動作可能時間は初回要求時から増えません)。この数値が1以上である場合、OSは、アプリがバックグラウンド処理を要求していると判断します。この回数は、endBackgroundTask:で減算されます。0になった時点で、OSはアプリケーションはバックグラウンド処理を望んでいないと判断します。メモリ管理のリファレンスカウントと同じ原理です。

言い換えれば、endBackgroundTask:を呼び出すまで、アプリケーションはバックグラウンド処理を要求し続けていることになります。例えば、アプリがフォアグラウンドへ復帰するなど、バックグラウンド処理が不要になった場合には、明示的にendBackgroundTask:を呼びださなければなりません。

Appleが公開しているiOS App Programming GuideExecuting a Finite-Length Task in the Backgroundでは言及されていないためか、「Cocoaの日々」「[iOS] バックグラウンド実行見本(Task Completion)」などを除いて、あまりフォアグラウンド復帰時の処理に関し、言及していないように感じています。

この動作を確認するために TaskCompletionSample を作成しました。

https://github.com/n-miyo/TaskCompletionSample

このアプリはアプリケーションバッジを1秒ごとに増加させます。beginBackgroundTaskWithExpirationHandler:の実行はviewDidLoadで行われ、endBackgroundTask:は実行されません。

アプリをバックグラウンドへ落とすと、バッジの増加を通じてバックグラウンド処理を確認できます。アプリを一度フォアグラウンドへ復帰させ、再度バックグラウンドへ遷移させてみます。この処理の過程では新たにbeginBackgroundTaskWithExpirationHandler:を呼び出していませんが、アプリはバックグラウンド実行を継続可能です。

画面の「clear at 'didBecomeActive'」スイッチをONにすると、- [AppDelegate applicationDidBecomeActive:]実行時、endBackgroundTask:を実行するようになります。
この状態で、一度アプリをバックグラウンドへ遷移させ、再度フォアグラウンドへ遷移させた後、再度アプリをbeginBackgroundTaskWithExpirationHandler:へ移行させると、endBackgroundTask:との呼び出し回数が釣り合い、以後バックグラウンドでの実行は行われません。

さて、バックグラウンド処理を要求する場合、時間切れ時に実行されるタスクを登録し、その中でもendBackgroundTask:を実行する必要があります。これらは定形で書けますが、毎回書くのは面倒なものです。

また、複数のタスクをバックグラウンド登録した場合、それらをフォアグラウンド復帰のタイミングで一斉にキャンセルするには、すべてのタスクIDを管理しなければなりません。

これらの比較的定形でありながら、毎回書くのが面倒になるコードをまとめたものが、先に公開した TMPTaskCompletionManagerになります。使い方などは、「iOS の task completion をサポートする TMPTaskCompletionManager を公開しました」を御覧ください。

ご意見などお待ちしております。

iOS の task completion をサポートする TMPTaskCompletionManager を公開しました

今更ではありますが、iOS の task completion をサポートする TMPTaskCompletionManager を公開しました。

https://github.com/n-miyo/TMPTaskCompletionManager

TMPTaskCompletionManagerは、バックグラウンドタスクの登録や、タスク終了時の登録解除処理など、定形的なコードを簡単に書くためのライブラリです。

タスクを登録するには、runBackgroundTask:taskQueue:expirationTask:メソッドで「バックグランドで実行したいタスク」と「時間切れ時に実行したいタスク」を登録します。戻り値は、バックグラウンドタスク識別子です。

「バックグラウンドで実行したいタスク」はtaskQueue:で指定したNSOperationQueue上で実行されます。nilを指定すればQueueは内部で自動生成されます。


処理が終了した場合等、バックグラウンド処理を登録解除したい場合には、cancelBackgroundTask:を使います。


フォアグラウンド復帰時など、すべての登録を解除したい場合には、cancelAllBackgroundTasks が利用できます。-[AppDelegate applicationDidBecomeActive]などで使うとよいでしょう。


各種ご意見など、お待ちしております。

2013/09/22

UISegmentedControlで選択されているSegmentのtapも検出できるライブラリを公開しました

iOSのUISegmentedControlは、複数要素から1つを選択する用途で便利に使えるコンポーネントです。

イベントの変化は -[UIControl addTarget:action:forControlEvents:] を用い、UIControlEventValueChangedを検出するのが一般的です。

但し、ラジオボタンのアナロジーである為でしょうか、現在選択されている Segment を再度選択しても、UIControlEventValueChangedは発火しません。

勿論UISegmentedControlのtouch系イベントは発火するので、それを用いることでtapを検出することが出来ます。

注意すべきは、iOS6以前とiOS7でUISegmentedControlのtap検出タイミングが変化したことです。

iOS6以前は所謂 Touch Down 時点をもって「Segment が選択された」とされていましたが、iOS7からは Touch Up Inside 時点をもっての選択へ変更されました。UIButton の挙動と整合した、という点では、わかりやすくなったと言えるでしょう。

このOSに伴う動作の差などを吸収し、既に選択されているSegmentの再tapを検出するためTPSegmentedControlをgithub で公開しました

UISegmentedControl に対する非常に小さな hack です。UISegmentedControl をそのまま置き換える形で利用できます。

最も単純にはLibディレクトリへ含まれてるTPSegmentedControl.hとTPSegmentedControl.mを自身のプロジェクトへ追加し、UISegmentedControlを使っていたコードを、TPSegmentedControlへ置き換えれば動作します。

CocoaPods へも対応していますのでPodsでインストールすることも可能です。
% edit Podfile
pod 'TPSegmentedControl'

ご意見、ご感想、pull request等お待ちしております。

2013/03/26

TPMigrationManagerをリリースしました

TPMigrationManagerをリリースしました。

TPMigrationManagerはCoreDataのマイグレーションを簡単に行うための便利ユーティリティです。

github から取得出来ます。MITライセンスです。

git clone https://github.com/n-miyo/TPMigrationManager.git

CoreDataはOS XやiOSで使えるフレームワークで、オブジェクトをファイルへ保存したり、関係性を管理したりすることができます。非常に平たく言ってしまえば、データベースとCocoaのオブジェクトを、便利に橋渡ししてくれるフレームワークです。

データを格納する為には、その格納情報を示したモデル(RDBMS的に言えばスキーマ)が必要になります。モデルを変更した場合、データの移行(マイグレーション)が必要になりますが、CoreDataはその仕組みも提供しています。

CoreDataのマイグレーション機能は非常に多機能ですが、多機能であるが故に、利用するための設定などがやや複雑です。

TPMigrationManagerは、できることを基本機能へ限定することで、なるべく簡単に利用できるようにしました。

マイグレーションは以下の3ステップで完了します。

  1. TPMigrationManagerのインスタンスを作る
  2. - [TPMigrationManager migrationStatus]でマイグレーションの方法を得る
  3. 取得した方法に基づき、マイグレーションを実行する

インスタンスを作成するには、モデルや格納ファイルの場所を明示的に指定する方法と、基本となる名前を引渡し、TPMigrationManagerへ推測させる方法の2種類があります。

前者が - [TPMigrationManager initWithManagedObjectModel:persistentStoreURL:persistentStoreType:]、後者が - [TPMigrationManager initWithBasename:]です。
Xcodeが生成するCoreDataのテンプレートに従っている場合には、後者が便利です。

CoreDataでは2つのマイグレーション方法を提供しています。

一つが「新旧モデルの変更をCoreDataへ自動推測させてマイグレーションを行う」方法、もう一つが「自分で変換のマッピングモデル作成しマイグレーションを行う」方法です。

TPMigrationManagerでは、前者は - [TPMigrationManager migrateByInferredMappingModelWithOptions:completed:]が、また後者は - [TPMigrationManager migrateBySpecificMappingModelWithOptions:progress:completed:]が相当します。

前者は事前準備が必要ない分、より簡単で、また動作も大変に高速です。Appleの推奨もこちらです。ただし、複雑な変更などへは対応出来ません。また、マイグレーションの進捗状況を取得することや、キャンセルを行うこともできません。

後者は、新旧のモデルを自由に変換することができます。例えば、あるモデルでは複数の要素へ分かれていたデータを1つへまとめる、といったことも可能です。進捗状況も取得出来ますし、キャンセルも可能です。但しマイグレーションには多くの時間が必要となります。また、変換方法を示すNSMappingModelを事前に準備し、アプリへ含めて置かなければなりません。

どちらの方法を使うにしても、メソッドを呼び出せば、マイグレーションは終了します。終了後は、指定したblocksが実行されます。

どうぞ、お気軽にご意見などお寄せ頂ければ嬉しく思います。お待ちしております。


2013/02/03

NSURLRequestのPOSTでTimeoutを機能させる

CocoaのNSURLRequestではタイムアウトを設定することができます。

NSURLRequestでは、initWithURL:cachePolicy:timeoutIntervalによるインスタンス生成時に指定可能ですし、NSMutableURLRequestでは、インスタンス生成後に変更も可能です。

但しiOS5以下の環境では、NSURLRequestインスタンスのタイムアウト値が再設定されてしまう条件が存在します。

その条件とは「インスタンスのタイムアウト値が240秒未満の場合」に「HTTPBodyを設定する」ことであり、これを行うと、タイムアウト値が240秒へ再設定されます。

NSMutableURLRequestインスタンスの場合、timeoutIntervalでタイムアウト値を最設定出来ますが、この場合にも240秒未満へ設定することはできません。

この仕様は古くからコミュニティの間で語られていますが(see:https://devforums.apple.com/message/108292/。要AppleDeveloperアカウント)、残念ながら、Appleのドキュメントへは明記されていないようです。

iOS6ではこの仕様は変更され、タイムアウト値が再設定されてしまうことはありません。ただし、iOS5環境をサポートするアプリケーションでは、アプリケーションで対応する必要があります。

対応策の一つは自身でタイマを設定し、タイムアウトになった時点でコネクションをcancelすることです。

これに基づいたライブラリ「TPTimeoutURLConnection」をgithubへ公開しました

同期通信はNSURLConnectionへのカテゴリとして実装しています。追加メソッドは、次の一つです。
+ sendSynchronousRequest:timeoutInterval:returningResponse:error:

timeoutInterval:引数へ、NSURLRequestで設定したかったタイムアウト値を指定します。例えば次のようにして使います。

ここでは、timeoutInterval:を通じて5秒のタイムアウトを設定しています。

一方非同期通信を行うには、NSURLConnectionを稼働させる際にタイマを起動し、自身が設定したdelegateの適切なメソッドでタイマの廃棄などを行わなければなりません。

そこで、先のライブラリでは雛形となるサンプル実装として、TPURLConnectionクラスと、TPURLConnectionDelegateが同梱されています。特にアプリケーション側の制約がなければ、どちらも継承して使うことが可能です。

例えば次のように利用できるでしょう。

注意すべきことは、NSURLRequestへHTTPBodyを設定した時点でtimeoutIntervalue値が変更されてしまってうことです。そのため、NSURLRequestインスタンス作成時に設定したtimeout値を、そのまま用いることはできません。

なお、このライブラリはiOS6でも動作しますが、iOS6はタイムアウトの制限が撤廃されていますので、利用する理由はありません。

iOS5系をサポートするアプリも少なくなりつつあるとは思いますが、ご意見などお待ちしております。