私のiPhoneアプリはコアデータストアを移行する必要があり、一部のデータベースは非常に大規模です。Appleのドキュメントでは、「マルチパス」を使用してデータを移行し、メモリ使用量を削減することを提案しています。ただし、ドキュメントは非常に限られており、実際にこれを行う方法については十分に説明されていません。誰かが私に良い例を示したり、実際にこれを実行する方法のプロセスを詳細に説明したりできますか?
私のiPhoneアプリはコアデータストアを移行する必要があり、一部のデータベースは非常に大規模です。Appleのドキュメントでは、「マルチパス」を使用してデータを移行し、メモリ使用量を削減することを提案しています。ただし、ドキュメントは非常に限られており、実際にこれを行う方法については十分に説明されていません。誰かが私に良い例を示したり、実際にこれを実行する方法のプロセスを詳細に説明したりできますか?
回答:
私はAppleが彼らのドキュメントで何を示唆しているのか理解しました。それは実際には非常に簡単ですが、それが明らかになるまでには長い道のりがあります。例を挙げて説明します。初期の状況はこれです:
これは、「コアデータストレージを備えたナビゲーションベースのアプリ」テンプレートを使用してプロジェクトを作成するときに取得するモデルです。私はそれをコンパイルし、forループの助けを借りてハードヒットを行い、すべていくつかの異なる値を持つ約2kのエントリを作成しました。そこで、NSDate値を持つ2.000イベントに行きます。
次に、次のようなデータモデルの2番目のバージョンを追加します。
違いは次のとおりです。Eventエンティティがなくなり、2つの新しいエンティティが追加されました。1つはタイムスタンプをaとして格納し、もう1つdouble
は日付をNSString
。として格納する必要があります。
目標は、すべてのバージョン1イベントを2つの新しいエンティティに転送し、移行に沿って値を変換することです。これにより、それぞれ2倍の値が、別々のエンティティの異なるタイプになります。
移行するには、手動で移行を選択します。これは、マッピングモデルを使用して行います。これはあなたの質問に対する答えの最初の部分でもあります。2kエントリの移行には時間がかかり、メモリフットプリントを低く抑えたいため、移行は2つのステップで行います。
これらのマッピングモデルをさらに分割して、エンティティの範囲のみを移行することもできます。100万件のレコードを取得したとすると、プロセス全体がクラッシュする可能性があります。Filter述語を使用して、フェッチされたエンティティを絞り込むことができます。
次のような最初のマッピングモデルを作成します。
1.新しいファイル->リソース->マッピングモデル
2.名前を選択し、StepOneを選択しました
3.ソースと宛先のデータモデルを設定します
マルチパス移行にはカスタムエンティティ移行ポリシーは必要ありませんが、この例の詳細を取得するために行います。そのため、エンティティにカスタムポリシーを追加します。これは常にのサブクラスですNSEntityMigrationPolicy
。
このポリシークラスは、移行を実行するためのいくつかのメソッドを実装します。ただし、この場合は単純なので、実装する必要があるメソッドは1つだけですcreateDestinationInstancesForSourceInstance:entityMapping:manager:error:
。
実装は次のようになります。
#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
はバンドルで終わります。
デスティネーションストアを提供する必要があり、ソースストアであってはなりません。移行が成功した後、古いものを削除し、新しいものの名前を変更できます。
マッピングモデルの作成後にデータモデルにいくつかの変更を加えたため、互換性エラーが発生しました。これは、マッピングモデルを再作成することによってのみ解決できました。
これらの質問は関連しています:
iPhoneで大規模なCoreDataデータストアを移行する際のメモリの問題
最初のリンクを引用するには:
これについては、「複数のパス」セクションの公式ドキュメントで説明されていますが、推奨されるアプローチは、移行をエンティティタイプごとに分割すること、つまり、それぞれがエンティティタイプのサブセットをから移行する複数のマッピングモデルを作成することです。完全なデータモデル。
データベーススキーマに、person、student、course、class、registrationなどの5つのエンティティがあり、標準的な例を使用するとします。この例では、学生がpersonをサブクラス化し、クラスがコースを実装し、登録がクラスと学生に結合します。これらすべてのテーブル定義に変更を加えた場合は、基本クラスから始めて、上に進む必要があります。したがって、各登録レコードはクラスと学生がそこにいることに依存するため、登録の変換から始めることはできません。したがって、Personテーブルのみを移行し、既存の行を新しいテーブルにコピーし、そこにある新しいフィールドに入力して(可能な場合)、削除された列を破棄することから始めます。自動解放プール内で各移行を実行します。これにより、移行が完了すると、メモリが再び開始されます。
Personテーブルが完成したら、studentテーブルを変換できます。次に、コース、クラス、最後に登録テーブルに移動します。
もう1つの考慮事項は、レコードの数です。Personのように1000行ある場合、100程度ごとに、リリースに相当するNSManagedObjectを実行する必要があります。これは、管理対象オブジェクトのコンテキストを通知することです[moc refreshObject:ob mergeChanges:番号]; また、古いデータタイマーを低く設定して、メモリが頻繁にフラッシュされるようにします。