複数のパスを使用したCoreData Migrationの例または説明?


85

私のiPhoneアプリはコアデータストアを移行する必要があり、一部のデータベースは非常に大規模です。Appleのドキュメントでは、「マルチパス」を使用してデータを移行し、メモリ使用量を削減することを提案しています。ただし、ドキュメントは非常に限られており、実際にこれを行う方法については十分に説明されていません。誰かが私に良い例を示したり、実際にこれを実行する方法のプロセスを詳細に説明したりできますか?


あなたは実際にメモリの問題で実行しましたか?移行は軽量ですか、それともNSMigrationManagerを使用しますか?
ニックウィーバー

はい、GDBコンソールはメモリ警告があったことを示しました、そしてそれからアプリは限られたメモリのためにクラッシュします。軽量移行とNSMigrationManagerの両方を試しましたが、現在はNSMigrationManagerを使用しようとしています。
ジェイソン

わかりました。何が変わったのか、もう少し詳しく説明していただけますか?
ニックウィーバー

最後に、私は見つけました、私の答えを読んでください。
ニックウィーバー

こんにちはジェイソン、質問のようなものを修正できますか?
Yuchen Zhong 2015

回答:


174

私はAppleが彼らのドキュメントで何を示唆しているのか理解しました。それは実際には非常に簡単ですが、それが明らかになるまでには長い道のりがあります。例を挙げて説明します。初期の状況はこれです:

データモデルバージョン1

ここに画像の説明を入力してください ここに画像の説明を入力してください

これは、「コアデータストレージを備えたナビゲーションベースのアプリ」テンプレートを使用してプロジェクトを作成するときに取得するモデルです。私はそれをコンパイルし、forループの助けを借りてハードヒットを行い、すべていくつかの異なる値を持つ約2kのエントリを作成しました。そこで、NSDate値を持つ2.000イベントに行きます。

次に、次のようなデータモデルの2番目のバージョンを追加します。

ここに画像の説明を入力してください

データモデルバージョン2

違いは次のとおりです。Eventエンティティがなくなり、2つの新しいエンティティが追加されました。1つはタイムスタンプをaとして格納し、もう1つdoubleは日付をNSString。として格納する必要があります。

目標は、すべてのバージョン1イベントを2つの新しいエンティティに転送し、移行に沿って値を変換することです。これにより、それぞれ2倍の値が、別々のエンティティの異なるタイプになります。

移行するには、手動で移行を選択します。これは、マッピングモデルを使用して行います。これはあなたの質問に対する答えの最初の部分でもあります。2kエントリの移行には時間がかかり、メモリフットプリントを低く抑えたいため、移行は2つのステップで行います。

これらのマッピングモデルをさらに分割して、エンティティの範囲のみを移行することもできます。100万件のレコードを取得したとすると、プロセス全体がクラッシュする可能性があります。Filter述語を使用して、フェッチされたエンティティを絞り込むことができます。

2つのマッピングモデルに戻ります。

次のような最初のマッピングモデルを作成します。

1.新しいファイル->リソース->マッピングモデル ここに画像の説明を入力してください

2.名前を選択し、StepOneを選択しました

3.ソースと宛先のデータモデルを設定します

ここに画像の説明を入力してください

マッピングモデルステップ1

ここに画像の説明を入力してください

ここに画像の説明を入力してください

ここに画像の説明を入力してください

マルチパス移行にはカスタムエンティティ移行ポリシーは必要ありませんが、この例の詳細を取得するために行います。そのため、エンティティにカスタムポリシーを追加します。これは常にのサブクラスですNSEntityMigrationPolicy

ここに画像の説明を入力してください

このポリシークラスは、移行を実行するためのいくつかのメソッドを実装します。ただし、この場合は単純なので、実装する必要があるメソッドは1つだけですcreateDestinationInstancesForSourceInstance:entityMapping:manager:error:

実装は次のようになります。

StepOneEntityMigrationPolicy.m

#import "StepOneEntityMigrationPolicy.h"


@implementation StepOneEntityMigrationPolicy

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance 
                                      entityMapping:(NSEntityMapping *)mapping 
                                            manager:(NSMigrationManager *)manager 
                                              error:(NSError **)error
{
    // Create a new object for the model context
    NSManagedObject *newObject = 
        [NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName] 
                                      inManagedObjectContext:[manager destinationContext]];

    // do our transfer of nsdate to nsstring
    NSDate *date = [sInstance valueForKey:@"timeStamp"];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
    [dateFormatter setDateStyle:NSDateFormatterMediumStyle];    

    // set the value for our new object
    [newObject setValue:[dateFormatter stringFromDate:date] forKey:@"printedDate"];
    [dateFormatter release];

    // do the coupling of old and new
    [manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];

    return YES;
}

最終ステップ:移行自体

NSDateをdoubleに変換するために使用されるtimeIntervalSince1970だけで、ほぼ同じ2番目のマッピングモデルを設定する部分はスキップします。

最後に、移行をトリガーする必要があります。今のところ、定型コードはスキップします。必要な場合は、こちらに投稿します。それはで見つけることができ移行プロセスのカスタマイズそれだけ最初の2つのコード例のマージだが。次のように第3の最後の部分が変更されます。代わりのクラスメソッド使用してのNSMappingModelクラスをmappingModelFromBundles:forSourceModel:destinationModel:、我々が使用するinitWithContentsOfURL:クラスメソッドは一つだけ、バンドル内の多分最初、見つかったマッピングモデルを返しますので。

これで、ループのすべてのパスで使用でき、移行メソッドを移行マネージャーに送信できる2つのマッピングモデルができました。それでおしまい。

NSArray *mappingModelNames = [NSArray arrayWithObjects:@"StepOne", @"StepTwo", nil];
NSDictionary *sourceStoreOptions = nil;

NSURL *destinationStoreURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataMigrationNew.sqlite"];

NSString *destinationStoreType = NSSQLiteStoreType;

NSDictionary *destinationStoreOptions = nil;

for (NSString *mappingModelName in mappingModelNames) {
    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:mappingModelName withExtension:@"cdm"];

    NSMappingModel *mappingModel = [[NSMappingModel alloc] initWithContentsOfURL:fileURL];

    BOOL ok = [migrationManager migrateStoreFromURL:sourceStoreURL
                                               type:sourceStoreType
                                            options:sourceStoreOptions
                                   withMappingModel:mappingModel
                                   toDestinationURL:destinationStoreURL
                                    destinationType:destinationStoreType
                                 destinationOptions:destinationStoreOptions
                                              error:&error2];
    [mappingModel release];
} 

ノート

  • マッピングモデルcdmはバンドルで終わります。

  • デスティネーションストアを提供する必要があり、ソースストアであってはなりません。移行が成功した後、古いものを削除し、新しいものの名前を変更できます。

  • マッピングモデルの作成後にデータモデルにいくつかの変更を加えたため、互換性エラーが発生しました。これは、マッピングモデルを再作成することによってのみ解決できました。


59
複雑な血の地獄。Appleは何を考えていましたか?
aroth 2012年

7
わかりませんが、コアデータが良いアイデアだと思うときはいつでも、よりシンプルで保守しやすいソリューションを見つけるように努力しています。
ニックウィーバー2012年

5
ありがとう!これは素晴らしい答えです。複雑そうに見えますが、手順を学べばそれほど悪くはありません。最大の問題は、ドキュメントがこのようにあなたのためにそれを詳しく説明していないことです。
ベントフォード2012年

2
移行プロセスのカスタマイズへの更新されたリンクは次のとおりです。この投稿が書かれてから移動しました。 developer.apple.com/library/ios/documentation/Cocoa/Conceptual/...
user1021430

@NickWeaverどのようにdestinationStoreURLを決定していますか?作成していますか、それとも移行プロセス中にコアデータシステムによって作成されますか????
dev gr 2014

3

これらの質問は関連しています:

iPhoneで大規模なCoreDataデータストアを移行する際のメモリの問題

iOSを使用したチャンクでのマルチパスコアデータ移行

最初のリンクを引用するには:

これについては、「複数のパス」セクションの公式ドキュメントで説明されていますが、推奨されるアプローチは、移行をエンティティタイプごとに分割すること、つまり、それぞれがエンティティタイプのサブセットをから移行する複数のマッピングモデルを作成することです。完全なデータモデル。


1
リンクをありがとう。問題は、複数のパスで設定する方法を実際に詳細に説明している人がいないことです。効果的に機能するように、複数のマッピングモデルをどのように設定する必要がありますか?
ジェイソン

-5

データベーススキーマに、person、student、course、class、registrationなどの5つのエンティティがあり、標準的な例を使用するとします。この例では、学生がpersonをサブクラス化し、クラスがコースを実装し、登録がクラスと学生に結合します。これらすべてのテーブル定義に変更を加えた場合は、基本クラスから始めて、上に進む必要があります。したがって、各登録レコードはクラスと学生がそこにいることに依存するため、登録の変換から始めることはできません。したがって、Personテーブルのみを移行し、既存の行を新しいテーブルにコピーし、そこにある新しいフィールドに入力して(可能な場合)、削除された列を破棄することから始めます。自動解放プール内で各移行を実行します。これにより、移行が完了すると、メモリが再び開始されます。

Personテーブルが完成したら、studentテーブルを変換できます。次に、コース、クラス、最後に登録テーブルに移動します。

もう1つの考慮事項は、レコードの数です。Personのように1000行ある場合、100程度ごとに、リリースに相当するNSManagedObjectを実行する必要があります。これは、管理対象オブジェクトのコンテキストを通知することです[moc refreshObject:ob mergeChanges:番号]; また、古いデータタイマーを低く設定して、メモリが頻繁にフラッシュされるようにします。


つまり、基本的に、古いスキーマの一部ではない新しいコアデータスキーマを作成し、データを新しいスキーマに手動でコピーすることを提案していますか?
ジェイソン

-1データベースを手動でマッピングする必要はありません。デプロイされたデータベースは、軽量移行または明示的なMappingModelsを使用して移行できます。
ベントフォード2012年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.