Core Dataの iCloud同期について僕は誤解していた。自分でiCloudにあるCoreDataのデータを取得してiPhone本体側のデータにマージするなどは自分でやるものとてっきり思っていたのだ。それはちがって、iPhone本体にあるローカルなデータとiCloud側のデータが自動で同期できる。すごく便利。でも考えておかなければならないことがありそうだということで、今回はその端緒となる話し「その1」です。
■見つけたサンプル
以下のサイトのサンプルコードと解説のとおりに作ってみたところ、iCloud同期が実際に確認できた。すごく良い記事である。
→ iOS How-To : Using Core Data with iCloud
なお、上記の記事にあるように Provisioning Profileが必要なので当然だが、iOSシミュレータではiCloudは使えない。実機が必要。
■Entitlementの書き方
上記の記事のように、まず AppIDを取得し、iCloudを Enable にして、Provisioning Profileを作りXCode4にインストールする。
次の手順、XCode4でEntitlementを有効にすると、Entitlementの iCloud-Key-Value や iCloud Containers の値には project bundle identifierだけが自動で追加される。上記のgoddess-gate.comの記事のスクリーンショットでは Individual IDである GBBYECND9 も含めているが、Xcode 4.2.1のデフォルトで生成される設定では、 Individual IDを記述せずbundle identifierだけ書くのでよいようだ。
Xcode 4.2.1 では .entitlements ファイルを見てみると、com.apple.developer.ubiquity-kvstore-identifier は $(TeamIdentifierPrefix)com.xxxxx.AppName のようになっていて$(TeamIdentifierPrefix)が自動で入るようになっているからだ。
これにあわせて
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
の中の、cloudURLの値には、
NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:@"GBBYECND9.com.xxxx.AppName"];
のように設定することになる。
なお、 $(TeamIdentifierPrefix)com.xxxxx.AppName の$(TeamIdentifierPrefix)とcom.xxxxx.AppName の間にはピリオドがないから、 @”GBBYECND9com.xxxx.AppName”ではないの?と思ったが、ピリオドは勝手に入るらしい。実はこの誤解で多分30分くらいは無駄にした僕。
■iCloud対応はstoreURLの指すデータのoptionなのだなあ
ここに書かれている、
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {}
の中身を改めて読むと、iCloud対応はstoreURLのoptionなのだなあと気づく。
options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES],
NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
@”AppName.store”, NSPersistentStoreUbiquitousContentNameKey,
cloudURL, NSPersistentStoreUbiquitousContentURLKey,
nil];
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {}
というのはつまり、僕は最初誤解していて、iCloudではiCloud用のsqliteファイルをiCloud上に別途作ってそれはそれで自分で管理するものと思っていたのだが、そうではなく storeURLというローカル(iPhone本体)に作ったsqliteファイルがあくまで主体としてあって、そのオプション設定としてiCloudがあるということが、コードから分かる。
このことが何を意味するかというと、iCloudに通信でつながらないときでも、storeURLが指すローカルのデータベースをガシガシ変更できて、そして後で通信がつながったときに、iOS5 が iCloud側のデータ同期を自動でしてくれるらしいということだ。実際に実験してみたらそのようになっていた。すごい!
■CoreDataの設計を変えたり、やり直しをしたい場合
cloudURLにはどんな値がはいっているのだろう。デバッガでトレースして見てみると、
file://localhost/private/var/mobile/Library/Mobile%20Documents/GBBYECND9~com~xxxxx~AppName…
みたいな値が入っている。
iCloud用にローカルにファイルを作っているみたいだ。一時ファイルのようなものだろうか。
CoreDataの設計を変えたり、やり直しをしたい場合にこれを消さなくてもいいのだろうか?というのが真っ先に起こる疑問だ。
実際、やり直しをするときにpersistentStoreCoordinatorの中でクラッシュしてしまうことがあった。そのような場合は、
- アプリをiPhoneから削除
- iOS設定のiCloud設定の「ストレージとバックアップ」>「ストレージを管理」で「書類およびデータ」のところにある自分の生成されたファイルを削除
というのをするのに加えて、
[[NSFileManager defaultManager] removeItemAtURL:cloudURL error:&error];
というのをしておいた方がいいのかも知れない。この削除に関しては以下のページに書かれていた。
→ Crash when adding persistent store (iCloud enabled) in app delegate
■2台のデバイスで試してみる(つづく)
2台のデバイスで試してみた。
両方でこの同じアプリを開いて、一方で更新してみると、数秒後にはそれがもう一方に反映される。感動的。
次に、一方を「機内モード」にして通信を遮断し、データを追加してみる。追加はこの上で問題なくできる。「機内モード」を解除して、しばらく待つと、それらのたまった変更が一気にもう一方に反映される。すごい。
しかし、何らかの原因で、双方のデータに矛盾が生じると、とたんに同期が止まる。多分どちらかはiCloudとは同期しているのだろう。iCloudと矛盾のある方が同期しなくなる。
上記のソースコードでは、こういう状況に対して何のエラーメッセージも用意されていない。これが次なる課題。矛盾があるとき、何らかのエラーメッセージを出してユーザーに解消方法を選んでもらうような処理を追加する必要があると思われる。
この課題について「その2」を書く予定です。




