Core Data
と CloudKit
のフレームワークを合わせて用いるプロジェクトが色々あります。以前いくつか問題点があったので、皆さんがプロジェクトでセットアップのエラーを防げるように、問題点の多くをリスト化しました。
プロジェクトでそのような問題が起きないように、アプリケーションを発行する前にこのチェックリストによく目を通しておいてください。
既存のCoreDataデーターベースのクラウド機能を設定するためには、コードのNSPersistentContainer
ではなく、単にNSPersistentCloudKitContainer
を使用してください。
1. ベーシックプロジェクト設定
iCloudコンテナー
必ず特定の名称フォーマットの iCloud CloudKit コンテナーを作成するようにして下さい:iCloud.あなたの_App_Bundle_ID
.
バックグラウンドモード
必ずXcodeの Background Modes
セクションで Remote notifications
を有効にして下さい。
2. さまざまなデータベース環境
CloudKit データベースがローカルの CoreData データベーススキーマと同一のスキーマであることを確認してください
シミュレーターでiOSアプリケーションを実行、あるいはXcodeを使ってiPhone上のアプリケーションを起動する場合は、お使いのCloudkit データベース環境は開発モードにあります。
CoreDataとCloudKitを使用するプロジェクトを設定すると、レコード作成イベントが発生したときに初めてデータベーススキーマが作成されます。データベーススキーマをローカルに設定するだけでは、クラウドのデータベーススキーマは作成されません。
システムがクラウドサーバー上に適切なデータベーススキーマを作成するためには、すべてのプロパティを持つレコードを作成する必要があります。
たとえば、name
、birthday
、favoriteToys
というプロパティを持つレコードタイプCat
があるとします。
.xcdatamodeld
ファイルでスキーマを定義した後も、 CloudKitデータベースにはスキーマが含まれません。
name
プロパティを使用してシミュレータ上にレコードを作成すると、 CloudKitデータベースは、 name
プロパティのみを含むCD_Cat
という名前の新しいスキーマを自動的に作成します。
CloudKitデータベースのスキーマをローカルスキーマと同じにするには、 name
、birthday
、favoriteToys
を含むテストレコードをローカルに作成する必要があります。
開発とプロダクション環境の違い
TestFlightまたはApp Storeを通じてアプリを実行すると、CloudKit データベース環境はプロダクションに設定されます。このプロパティを .entitlements
ファイルに追加することでアプリを手動でプロダクション環境に設定することもできます:
<key>com.apple.developer.icloud-container-environment</key>
<string>Production</string>
開発データベースと本番データベース間のデータが同じではありません。
3. マージポリシーを設定する
Core Data には様々なマージポリシーがあります。マージポリシーは、リモートデータとローカル (メモリ内の) データの間に競合がある場合に処理します。
手動で処理することができますが、この記事ではマージポリシーを使用します。
データベースが自身に変更を書き込もうとする際には、コンフリクトが発生することがあります。(例えばオブジェクトの重複など。)
デフォルトで、Core Data
フレームワークは例外を発生させます。
viewContext
の mergePolicy
プロパティーを設定することで、コンフリクトの解消を試みることができます。
self.viewContext.mergePolicy = NSMergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType)
以下は一部の結合ポリシーの説明です。
Name | Explanation |
---|---|
NSErrorMergePolicyType | デフォルト。try viewContext.save() を呼び出すと、例外が報告されます。 |
NSMergeByPropertyStoreTrumpMergePolicyType | メモリ内の変更は外部変更を置き換えます |
NSMergeByPropertyObjectTrumpMergePolicyType | 外部変更はメモリ内の変更を置き換えます。 |
NSOverwriteMergePolicyType | メモリ内のオブジェクト全体が永続ストアにプッシュされます。 |
NSRollbackMergePolicyType | 対立する変更されたオブジェクトについて、すべての状態が破棄されます |
通常、NSMergeByPropertyObjectTrumpMergePolicy
を使用すると、Core Data
はデータベースのデータバージョンを新しい変更で自動的に上書きします。
ここではCoreDataフレームワークのヘッダーファイルから説明します。
// This singleton policy merges conflicts between the persistent store's version of the object and the current in memory version. The merge occurs by individual property. For properties which have been changed in both the external source and in memory, the in memory changes trump the external ones.
@available(iOS 3.0, *)
public var NSMergeByPropertyObjectTrumpMergePolicy: AnyObject
メモリ内の変更が外部の変更を上書きするという意味です。
4. クラウドからの変更を自動的にマージします
Core Data がクラウドから変更を自動的に取得し、ローカルデータベースに適用できるようにする必要があります。
localStorageContext.automaticallyMergesChangesFromParent = true
5. まったく同じ値のオブジェクトを作成しないでください
データベースに保存する各オブジェクトにランダムなIDを追加し、同じオブジェクトが2つ存在しないようにしてください。
6. 複数のNSManagedObjectContext
の利用は避けましょう
クラスの共有インスタンスを生成し、データベースを管理してください。複数のデータベースコンテキストを操作する場合、そのプログラムに問題が生じる場合があります。
class PersistenceController {
static let shared = PersistenceController()
let localStorageContext: NSManagedObjectContext
init() {
let container = NSPersistentCloudKitContainer(name: "[.xcdatamodeldファイルの名前]")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
print(error.userInfo)
}
})
localStorageContext = container.viewContext
localStorageContext.automaticallyMergesChangesFromParent = true
localStorageContext.mergePolicy = NSMergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType)
}
}
7. ローカルの変更がiCloudにアップロードされるかどうかを確認します
シミュレータ上のすべてのレコードタイプとすべてのプロパティをテストする必要があります。テストを行った後、https://icloud.developer.apple.com
に移動し、作成したレコードがそこに存在することを確認します。
メニューからアプリケーションを選択し、Development
データベースの Records
オプションを選びます。データベースをプライベートデータベースに設定します(なぜなら、コアデータは設定をプライベートデータベースに保存するからです)。ゾーンを com.apple.coredata.cloudkit.zone
に設定し、Type:
を CD_[あなたのデータ型]
に設定します。
クエリー操作を許可してください
レコードに対する問い合わせを行う前にクエリー操作の許可を求められる場合があります。
トップメニューを選択し、Schema
をクリックしてください
その後、タイトル Record Type
をクリックし、Indexes
オプションを選択してください
データ型名 (CD_[あなたのデータ型]
) を選択し → Add Index
をクリックし → recordName
を選択し → Save Changes
をクリックして下さい
これで保存されたコアデータの全記録を見て、記録が適切にバックアップされているかが確認できるようになっているはずです。
テストアプリを走らせる際のアカウントはアップル開発者アカウントと同じものにして下さい。アクセスできるのは、ご自身の開発者アカウントのiCloudにあるプライベートデータのみです。
また、製品に変更を施す場合には、その前に必ずインデックスキー recordName
を削除して下さい。このインデックスは不要です。
8. 制作環境に変化を加えましょう
ギアのアイコンをクリックし、Deploy Schema to Production
をクリックすることで、開発環境から制作へと変化を加えることができます。アップストアでアプリを公開する前にそれを行う必要があります。
9. データベーススキーマに変更を加えるたびに、新しいデータベースバージョンを作成し、その新バージョンを現在のデータベースとして設定してください
Core Data ローカルスキーマに変更を加える際には、システムがデータの移行方法を把握できるよう新たなデータベースバージョンを作成する必要があります。また、プロパティの削除と追加を同時に行うことは(データを移行するコードを手作業で記述しない限り)お勧めしません。
私は通常、プロパティを削除せずに新しいプロパティを追加するだけにしています。
新しいデータベーススキーマバージョンを作成するには、データベースモデルファイルを選択し、最上部の Editor
メニューをクリックして Add Model Version
を選択してください。
Finish
ボタンをクリックすると、データベースの新しいバージョンを作成できるようになります。
次に新しく作成したデータベースモデルのバージョンをクリックします(この例ではInkMemo 3.xcdatamodel
)。修正しましょう。
そして右側のバーで、新しいデータベースバージョンを現在のバージョンとして選択してください。
データベースモデル内での変更が反映されるよう、コードファイルの更新を行ってください。
他の設定
また、 .xcdatamodel
ファイルをクリックしてから、 CONFIGURATIONS
セクション内の Default
をクリックして、Used with CloudKit
をトグルしてください
この設定が機能に大きな影響を与えることはないと思います。この設定をオンにしなくても、データは普通にCloudKitにバックアップされているようです...
デバッグのヒント
アプリを実行するとコンソールに多くのログメッセージが表示されるはずです。これらのメッセージを必ず読むようにしてください。また、CloudKitダッシュボードで、すべてのデータベーススキーマが作成され、適切にデプロイされていることを確認してください。
ここでは、おおよその情報を得るために追加できるデバッグ引数をいくつか紹介します。
-com.apple.CoreData.SQLDebug 3
-com.apple.CoreData.Logging.stderr 3
-com.apple.CoreData.ConcurrencyDebug 3
-com.apple.CoreData.MigrationDebug 3
-com.apple.CoreData.CloudKitDebug 3
これらの引数は、Run
モードのArguments Passed On Launch
セクションで追加することができます。数値が大きいほど、Xcodeコンソールでより詳細なデバッグメッセージを得ることができます。
また、シミュレーターでCloudKitをテストする際は、実際のデータを作成し、CloudKit Dashboardに行ってiCloudにデータがきちんとアップロードされているか確認してください。
この記事が Core Data + CloudKit の問題の修正にお役に立てますように。