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

2014/09/21

iPhone 6 上でのアプリ解像度

iPhone 6 は「デスクトップクラスのスケーラを実装している」とiPhone 6/6 Plus発表イベントで説明されました(キーノート15:50近辺参照)。

これを活かす形で、iPhone 6とiPhone 6 Plusには「拡大」モードが搭載されています。「設定」アプリから「画面表示と明るさ」を選択すると「画面表示の拡大」メニューがあり、ここで切り替え可能です。


また、iPhone 6/6 Plusへ最適されていない旧来のアプリもこのスケーラを活用し、画面全体へ拡大されるようになっています。DotByDotではないため、ややフォーカスは甘くなりますが、iPadでiPhone アプリを使う時のような黒い余白や拡大感は感じません。

例えば、本稿記載時点でiPhone 6へ最適化していない「JR東日本アプリ」をiPhone 6で動作させたスクリーンキャプチャです。違和感無く綺麗に表示されています。


一方こちらはiPhone 6へ最適化されているiBookのスクリーンキャプチャです。Status Bar上の文字サイズなどで、最適化している事を確認できます。


iPhone 6 に於いて「拡大」モードを用いると、iPhone 5/5sと同一解像度になります。

一方、iPhone 6 Plus で「拡大」モードを用いると、iPhone 6の「標準」モードと同一解像度になります。1機種分繰り下がるイメージです。

アプリをiPhone 6/6 Plusの解像度へ最適化させるには、iPhone 6/6 PlusサイズのLaunch Screen Fileを用意する必要があります。

iOS8専用アプリであれば、.xcodeproj の General にある「App Icons and Launch Images」で「Launch Screen File」を設定すると良いでしょう。

iOS7もサポートする場合には、「Launch Images Source」でiPhone 6/6 Plus用の画像を設定します。両者が設定されている場合には、Launch Screen Fileが優先されるようです(但しドキュメント上の明確な記載はありません)。

各種設定でアプリの解像度がどのように変化するか調べる為の動作確認アプリとして「ViewSizeChecker」を作成しました。github から取得可能です。

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

「設定」画面で「標準」モードと「拡大」モードを切り替えたり、ソースコード内の、
「Launch Screen File」や「Launch Images Source」を変更してみると、各種挙動が分かりやすいと思います。

2014/05/18

iTunes Storeで購入した曲と同じ曲がiTunes Matchで重複した場合

iTunes Matchを利用していて気がついたこと」の補足です。

ライブラリ内に同じ曲が複数存在している場合、それらがiTunes Matchで一致と認定されると、「重複」曲と判断されます。


しかし、iTunes Storeで購入した曲がライブラリ内へ存在しても、その「購入した曲」と「マッチた曲」は「重複」とは判定されません。同じ曲がiCloud上で併存することになります。


恐らく購入した曲は25000曲制限の対象外である、などの仕様が関係してくると思いますが、頭の片隅にとどめておくと、ライブラリ整理の際に役立つかもしれません。

以前CDからリッピングしたものの、つい忘れてiTunes Storeで購入した曲があり、この状態へ気が付きました。

2014/05/16

NSLinguisticTagger classのサンプルアプリケーションを公開しました

iOS5では、自然言語解析を行うNSLinguisticTaggerクラスが導入されました。

このクラスを使えば、トークン形式の識別や、形態素解析、名詞種別などを判定することが可能です。但し、サポートされている機能は、解析対象言語で変化します。iOS5での登場からiOS7に至るまで、日本語のサポートはイマイチです。

精度やサポート機能などを調べるため、指定した文章を解析するためのサンプルアプリケーションを GitHub で公開しました。

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

試してみて分かったのですが、残念ながら、形態素解析は英語解析でしか機能しないようです。ヨーロッパ系言語はサポートしているのだろう、と思っていただけに、やや意外でした。

日本語で名詞種別判定ができるようになると、自然文切り出しの有益性が高まり楽しさも増すと思います。そろそろ開発者向け公開が近づく、次期OSへ期待したいところです。

2014/05/15

iTunes Matchについて

待ちに待ったiTunes Matchが、日本でも開始されました。

既に仕様や、使用感に関するもの、また、トラブルとその回避方法など、様々なレポートが公開されており参考になります。

以下では私が使用した中で、興味を持った点をまとめてみました。


1. iTunes Plusアップグレードの復活

「iTunes Plusアップグレード」はiTunes Storeで買った楽曲を、DRMなし且つ高ビットレート楽曲へ、有償で変換してくれるサービスです。iTunes Matchの「マッチした低ビットレートの楽曲をアップグレードする」機能の元ネタと呼べるサービスでした。

各国で展開され、日本でもある時期まで実施されていましたが、iTunes Matchの開始と共に、ひっそりとサービスが終了しました。

私はDRMが付された楽曲を幾つか持っていたのですが、アップグレードを行う前にサービスが終了してしまい、これらの楽曲は、Apple以外の機器で聞くことができない状態となっていました。

今回iTunes Matchの開始により、これらの楽曲をDRMなしの楽曲へ変換することができ、大きく可搬性が高まりました。私にとっては、このアップグレード代金だけでiTunes Matchの年間使用料を回収できました。


2. マッチした楽曲は「購入した楽曲」扱いとはならない

iTunes Store外から入手した楽曲でありながら、iTunes Store上へ存在する楽曲は「マッチ」状態になり、iTunes Store上にある楽曲と置き換えたり、ストリーミングすることが可能になりますが、「購入した楽曲」とは明確に区別されます。

例えばiTunes Store上で購入した楽曲を、iTunesアプリなどを使ってStore上で表示させた場合、楽曲金額欄が「再生」となり、その場で再生が可能となります。



一方「マッチ」した楽曲は、以前購入対象のままであり、Preview以外の再生はできません。


また、購入した楽曲でないため、「マッチ」した楽曲も25000曲の制限対象となります。


3. iTunes経由で既にiOSデバイスへsyncされている楽曲は、iTunesMatch有効時、ダウンロードされた楽曲として扱われる

iTunesと接続し転送された楽曲が存在するiOSデバイスで、iTunesMatchを有効にすると「iTunes Matchはこのデバイスのミュージックライブラリに置き換わります。」と表示されます。

しかし、これによりデバイス内の楽曲が全て削除されるわけではありません。これら楽曲は、iTunes Match環境下で「ダウンロードされた楽曲」として利用されます。

この仕組みを利用し、よく聞く楽曲を事前に転送しておいてからiTunes Matchを有効にすると、ネットワークからのダウンロード量を減らすことができます。ダウンロード作業を減らすことが出来ますし、使用量制限のあるネットワークでは有益かもしれません。

なお、この機能が意図された仕様かどうかは明記されていませんので、将来的に変更される可能性もあります。


4. iOSデバイスでキャッシュされた楽曲はAirplaneモードでも再生可能

iCloud上にのみ存在し、iPhoneやiPad上へダウンロードされていない楽曲は、明示的にダウンロードを行わない限り、ストリーミング再生されます。

この際、再生した楽曲はデバイスへ自動的にキャッシュされています。キャッシュされた楽曲は、廃棄されない限り、Airplaneモードなどのネットワークが使えない環境でも再生可能です。

なお、キャッシュ廃棄のタイミングは明確に述べられていませんので、運用には注意が必要です。

勿論、明示的にダウンロードされた楽曲は、ネットワークが使えない環境でも再生可能です。


5. マッチした置き換え可能な楽曲も、自動的には置き換わらない

iTunesライブラリ内の全楽曲はマッチ対象か否かを判定されます。

しかし、よりビットレートの高い、または、DRM制限の緩い楽曲がiTunes Store上へ存在した場合でも、ライブラリ内のマッチ楽曲が自動的に置き換わるわけではありません。ライブラリ内の楽曲を置き換えたい場合には、ライブラリ内の楽曲を明示的に削除し、再ダ
ウンロードする必要があります(この際、iCloudからは削除してはいけません)。

この作業には、たとえば「iTunes MatchのAAC 256kbpsファイルへアップグレードしたい音質の悪い曲を見つけるためのスマートプレイリスト。」などが参考になります。


6. iTunes上のプレイリストも、すべて同期される

楽曲だけではなく、iTunes上で作成したプレイリストも、各デバイスで自動的に同期されるようになります。


自分の好きな音楽が、デバイスの容量に制限されず自由に聞けるのは、非常に便利だと感じています。

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/11/04

NSURLConnection / NSURLSession に於けるタイムアウト

通信を行うアプリケーションでは、不慮の事態等に備え、タイムアウト処理を実装することが望まれます。特にUIを備えた人間の利用するアプリケーションの場合には、より一層、タイムアウト処理へ気を使う必要があります。

タイムアウト値の選択には、いくつかの異なる視点があります。

例えば、サーバが落ちていたり、サーバまでの経路が切断している場合など、サーバとの接続を確立できない場合は、即座に処理を中断できるよう短いタイムアウトが望まれます。この状況は、待ち時間を長くしても解決できる見込みが少ないためです。

一方、伝送終了までの時間を制限したい場合には、伝送品質や利用回線の状況なども考慮し、先程の例よりも長めのタイムアウト値を設定することが望まれます。この場合、長めの時間を許容することで、通信の終了できる可能性が高まるためです。

iOSやOS XのNSURLRequestでは、初期化時のtimeoutInterval引数によってタイムアウトを設定できます。NSURLRequestを利用するNSURLConnectionクラスは、ここで設定されたタイムアウトを利用します。

iOS5.xまでのNSURLConnectionでは、タイムアウト処理に関し、ドキュメントへ記載されていない制限がありました。それは、HTTP Bodyを持った通信は、NSURLRequestのtimeoutIntervalの値に関わらず、240秒以下のタイムアウトが有効にならない、というものです(この制限を回避するために作成したライブラリが「NSURLRequestのPOSTでTimeoutを機能させる」で紹介したTPTimeoutURLConnectionでした)。恐らく、以前はセルラー系伝送速度が特に遅かったこともあり、通信が途中で打ち切られてしまう事態を避けるための仕様だったのだと思います。

この制約はiOS6.xで変更され、240秒以下のタイムアウトも有効になりました。しかし、このタイムアウト値は伝送処理の時間監視に利用されるため、指定時間を過ぎると通信が終了してしまいます。結果、極端に短い値に設定することはできず、通信不能状況、すなわち、接続できない状況の検出目的で使うのは難しい仕様でした。

幸い、iOS7やOS X 10.9で導入されたNSURLSessionでは、この問題に対し、2つのタイムアウト設定を提供する、ということで解決を試みています。

NSURLSessionでは、Sessionの各種設定をNSURLSessionConfigurationインスタンスを通じて行います。NSURLSessionConfigurationインスタンスでは、timeoutIntervalForRequestとtimeoutIntervalForResourceの2つのプロパティを通じ、2種類のタイムアウトを設定可能です。

timeoutIntervalForRequestは、データ間のタイムアウトを設定するプロパティです。このタイマは新しいデータが到着するとリセットされます。呼の確立までの間や、データが複数個へ分割されている場合など、各データ間のタイムアウト値が設定可能であり、通信が確立できないまま、または切れてしまったまま、長時間待ち状態になる事態を回避できます。

timeoutIntervalForResourceは、従来のNSURLRequestで使われていたtimeoutIntervalに近い機能を持つもので、URLで指定されたデータの取得処理が完了するまでの時間を指定するものです。これは、伝送速度が非常に遅い環境など、通信完了まで非常に長い時間を要してしまう状況を回避するために利用できます。

大変に便利な仕様なのですが、しかし、iOS7.0.3で動作確認を行ったところ、timeoutIntervalForRequestは、データを受信時にタイマがリセットされる状況を作り出すことが出来ませんでした。またその為か、timeoutIntervalForRequestとtimeoutIntervalForResourceの両者を設定した場合、単純に、より短い値がtimeoutIntervalForResource用のタイムアウト値として使われていました。ドキュメントへ書いてある「データ」が、今回の利用想定と異なっているのかもしれません。

さて、NSURLSessionでは、各通信処理をNSURLSessionTask(またはその継承クラス)のインスタンスで取り扱いますが、このTask生成時にNSURLRequestを指定することができます。この場合、NSURLSessionConfigutationによるタイムアウト値と、NSURLRequestによるタイムアウト値が個々に設定可能できることになります。生憎、これらの値の競合に関し、ドキュメント上は記述がありません。

同じくiOS7.0.3で挙動を調査したところ、NSURLRequestへ設定されたtimeoutInterval値は、NSURLSessionでは意味を持たないようです。このtimeoutInterval値が有効なタイムアウト値として利用されることはありませんでした。勿論NSURLSessionConfigurationでタイムアウト値を設定した場合には、その値が利用されます。

今回、上記の調査を行うために、NetworkTimeoutSampleという小さなアプリケーションと、それと共に用いるサーバアプリケーションを作成しました。

クライアントアプリケーションはiOS7上で動作し、NSURLConnection+NSURLRequest、NSURLSession+NSURLSessionConfiguration、NSURLSession+NSURLSessionConfiguration+NSURLRequestの組み合わせで、各タイムアウトを設定し、動作を確認することを目的としています。

サーバアプリケーションは任意のHTTP要求を受け付け、2行のbodyを持つHTTP 1.0レスポンスを返す処理を行いますが、clientからの接続をacceptする前、HTTP Header送出前、1行目のHTTP Bodyを返す前、ならびに、2行目のHTTP Bodyを返す前、それぞれに秒単位のwaitを挿入することができます。

タイムアウト処理の動作確認等へご利用ください。

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/07/21

Notification Center と UIApplicationDelegate

iOSのUIApplicationDelegate ProtocolはApplicationに関するイベントを伝達するためのプロトコルです。

その中の「- (void)applicationDidBecomeActive:(UIApplication *)application」は、APIドキュメントによれば、「非アクティブステートとアクティブステート遷移を知らせる際に呼び出される」と定義され、「アプリケーションが起動された時」や「電話着信やSMSメッセージ等による中断をユーザが無視した際」に呼ばれる、と書かれています。

また「- (void)applicationWillResignActive:(UIApplication *)application」は、同じくAPIドキュメントへ「電話着信やSMSメッセージ等による一時的な中断」や「ユーザがアプリケーションを中断し、バックグラウンド状態へ遷移し始めた際」に呼ばれる、と書かれています。

しかし、APIドキュメントに明示されていないものの、これらの両メソッドは、利用者がiOSのNotification Center(NSNotificationCenter ではなく、ステータスバーから引き出す「通知センター」)を呼び出した際にも呼び出されます

具体的には、

  •  Notification Center画面を少しでも表示させると、- [UIApplicationDelegate applicationWillResignActive:] が呼び出される
  • Notification Center画面を完全に画面外へ追い出すと、- [UIApplicationDelegate applicationDidBecomeActive:]が呼び出される

ことになります。

なおこれは、Notification Centerに関わらず、ユーザ指示により呼び出される「アプリケーション画面へ覆いかぶさる形式で表示されるシステムメニュー画面」でも同様の挙動となるようです。

実験の為、ただApplicationDelegateの各種メソッド呼び出しを、コンソールへ表示する為だけのサンプルアプリをGitHubへ置いてあります

2013/07/01

iOSアプリでのアイコン設定

iOSアプリでアイコンを設定する場合、「App-Info.plist」ファイルの「CFBundleIcons」key内にある「CFBundleIconFiles」keyへArray形式でアイコンを列挙します。例えば次のような形式になるでしょう。
        <key>CFBundleIcons</key>
        <dict>
                <key>CFBundlePrimaryIcon</key>
                <dict>
                        <key>CFBundleIconFiles</key>
                        <array>
                                <string>Icon-72.png</string>
                                <string>Icon-72@2x.png</string>
                                <string>Icon-Small.png</string>
                                <string>Icon-Small-50.png</string>
                                <string>Icon-Small@2x.png</string>
                                <string>Icon-Small-50@2x.png</string>
                        </array>
                        <key>UIPrerenderedIcon</key>
                        <true/>
                </dict>
        </dict>

しかし、特定の環境下構築した場合、この「CFBundleIconFiles」へ結びついたArray内へ、Default Screenのファイル一覧が含まれている場合があります。具体的には、次のような記述が含まれていました。
                               <string>Default-Portrait@2x~ipad.png</string>
                               <string>Default-Landscape@2x~ipad.png</string>
                               <string>Default-Landscape~ipad.png</string>
                               <string>Default-Portrait~ipad.png</string>
                               <string>Default-Portrait~ipad.png</string>
                               <string>Default-Portrait~ipad.png</string>
                               <string>Default-Landscape~ipad.png</string>
この設定が存在する場合、特定のiOS環境では、アプリアイコンが正しく表示されず、Default Screenをアイコンサイズへ縮小した画像が使われてしまいます。

もし「アプリアイコンがDefaut Screenになってしまう」という状況へ陥っている場合、「CFBundleIconFiles」keyへ紐付いたArrayの内容を確認すると良いでしょう。

2013/05/23

KiwiでNSManagedObject派生クラスへstubを設定する

KiwiはiOSでBDDを実施するためのライブラリです。Mock作成の仕組みなどが組み込まれており、他ライブラリを別途インストールすることなく、素早くテストを行うことができます。

但し、CoreDataのテストを行う目的で、NSManagedObject派生クラスへstubを設定する場合には注意が必要です。そのEntityが持つAttributeと紐付いたPropertyをStubにしようとしても実行時エラーになってしまいます。

例えばBookというEntityを作りauthorというAttributeを設定します。このEntityから生成されるBookクラスは次のようなコードになります。
@interface Book : NSManagedObject
@property (nonatomic) NSString *author;
@end
このauthorをStubにしようと、次のようにKiwiのテストケースを書いたとします。
it(@"foo", ^{
  Book *book = [KWMock mockForClass:[Book class]];
  [book stub:@selector(author) andReturn:@"Anonymous"];
  [[book.author should] equal:@"Anonymous"];
});
残念ながらこのテストは、
[[book.author should] equal:@"Anonymous"];
の実行でエラーになってしまいます。

このような場合、authorを@protocolで括りだし、そのプロトコルに対してテストを行うのが簡単です。

まず、テストしたいAttributeを含むプロトコルを作成します。
@protocol BookAttributes
@property (nonatomic) NSString *author;
@end
次に、NSManagedObjectを継承したBookクラスでこのプロトコルを実装します。
@interface Book : NSManagedObject <BookAttributes>
@end
テストではProtocolのMockを作成し、そのStubを実装します。
it(@"foo", ^{
  Book *book = [KWMock mockForProtocol:@protocol(BookAttributes)];
  [book stub:@selector(author) andReturn:@"Anonymous"];
  [[book.author should] equal:@"Anonymouss"];
});
これでNSManagedObject派生クラスが持つAttributeに対してもテストができるようになります。

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系をサポートするアプリも少なくなりつつあるとは思いますが、ご意見などお待ちしております。