多次传递核心数据迁移的示例或解释?

发布于 2024-11-07 10:28:51 字数 342 浏览 0 评论 0原文

我的 iPhone 应用程序需要迁移其核心数据存储,并且某些数据库非常大。 Apple 的文档建议使用“多次传递”来迁移数据以减少内存使用。但是,该文档非常有限,并且没有很好地解释如何实际执行此操作。有人可以给我指出一个很好的例子,或者详细解释如何实际实现这一目标的过程吗?

My iPhone app needs to migrate its core data store, and some of the databases are quite large. Apple's documentation suggests using "multiple passes" to migrate data to reduce memory use. However, the documentation is very limited and doesn't explain very well how to actually do this. Can someone either point me towards a good example, or explain in detail the process of how to actually pull this off?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

残月升风 2024-11-14 10:28:52

我已经弄清楚苹果在他们的 文档。这实际上很简单,但要变得明显还有很长的路要走。我将用一个例子来说明这一解释。初始情况是这样的:

数据模型版本 1

在此处输入图像描述
在此处输入图像描述

这是使用“具有核心数据存储的基于导航的应用程序”模板创建项目时获得的模型。我编译了它,并在 for 循环的帮助下做了一些努力,创建了大约 2k 个条目,所有条目都有一些不同的值。我们有 2000 个具有 NSDate 值的事件。

现在我们添加数据模型的第二个版本,如下所示:

在此处输入图像描述

数据模型版本 2

的区别是:事件实体消失了,我们有了两个新实体。第一个将时间戳存储为 double,第二个将日期存储为 NSString

目标是将所有版本 1 事件传输到两个新实体并在迁移过程中转换值。这会导致值的两倍,每个值在单独的实体中作为不同的类型。

为了迁移,我们选择手动迁移,并使用映射模型来完成此操作。这也是回答你问题的第一部分。我们将分两步进行迁移,因为迁移 2k 条目需要很长时间,而且我们希望保持较低的内存占用量。

您甚至可以继续进一步拆分这些映射模型以仅迁移实体范围。假设我们有一百万条记录,这可能会使整个过程崩溃。可以使用 过滤谓词

回到我们的两个映射模型。

我们创建第一个映射模型,如下所示:

1。新文件 ->资源->映射模型
在此处输入图像描述

2.选择一个名字,我选择StepOne

3。设置源和目标数据模型

在此处输入图像描述

映射模型第一步

在此处输入图像描述

在此处输入图像描述

在此处输入图像描述

多次迁移不需要自定义实体迁移策略,但我们会这样做以获得更多详细信息对于这个例子。因此,我们向实体添加自定义策略。它始终是 NSEntityMigrationPolicy 的子类。

在此处输入图像描述

此策略类实现了一些方法来实现迁移。然而,在这种情况下很简单,因此我们只需实现一种方法: 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 转换为双精度的 timeIntervalSince1970 。

最后我们需要触发迁移。我现在将跳过样板代码。如果你需要的话,我会在这里发布。它可以在 自定义迁移过程 它只是前两个代码示例的合并。第三部分也是最后一部分将修改如下:而不是使用 NSMappingModel 的类方法mappingModelFromBundles:forSourceModel:destinationModel:< /code>我们将使用 initWithContentsOfURL: 因为类方法将仅返回一个(也许是第一个)在包中找到的映射模型。

现在我们已经有了两个映射模型,可以在循环的每次传递中使用它们并将 migrate 方法发送到迁移管理器。就是这样。

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 结尾。

  • 必须提供目标存储,并且不应是源存储。成功迁移后,您可以删除旧的并重命名新的。

  • 创建映射模型后,我对数据模型做了一些更改,这导致了一些兼容性错误,我只能通过重新创建映射模型来解决。

I've figured out what Apple hints in their documentation. It's actually very easy but a long way to go before it's obvious. I'll illustrate the explanation with an example. The initial situation is this:

Data Model Version 1

enter image description here
enter image description here

It's the model you get when you create a project with the "navigation based app with core data storage" template. I compiled it and did some hard hitting with some help of a for loop to create around 2k entries all with some different values. There we go 2.000 events with an NSDate value.

Now we add a second version of the data model, which looks like this:

enter image description here

Data Model Version 2

The difference is: The Event entity is gone, and we've got two new ones. One which stores a timestamp as a double and the second one which should store a date as NSString.

The goal is to transfer all Version 1 Events to the two new entities and convert the values along the migration. This results in twice the values each as a different type in a separate entity.

To migrate, we choose migration by hand and this we do with mapping models. This is also the first part of the answer to your question. We will do the migration in two steps, because it's taking long to migrate 2k entries and we like to keep the memory footprint low.

You could even go ahead and split these mapping models further to migrate only ranges of the entities. Say we got one million records, this may crash the whole process. It's possible to narrow the fetched entities down with a Filter predicate.

Back to our two mapping models.

We create the first mapping model like this:

1. New File -> Resource -> Mapping Model
enter image description here

2. Choose a name, I chose StepOne

3. Set source and destination data model

enter image description here

Mapping Model Step One

enter image description here

enter image description here

enter image description here

The multi pass migration doesn't need custom entity migration policies, however we will do it to get a bit more detail for this example. So we add a custom policy to the entity. This is always a subclass of NSEntityMigrationPolicy.

enter image description here

This policy class implements some methods to make our migration happen. However it's simple in this case so we will have to implement only one method: createDestinationInstancesForSourceInstance:entityMapping:manager:error:.

The implementation will look like this:

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;
}

Final step: the migration itself

I'll skip the part for setting up the second mapping model which is almost identical, just a timeIntervalSince1970 used to convert the NSDate to a double.

Finally we need to trigger the migration. I'll skip the boilerplate code for now. If you need it, I'll post here. It can be found at Customizing the Migration Process it's just a merge of the first two code examples. The third and last part will be modified as follows: Instead of using the class method of the NSMappingModel class mappingModelFromBundles:forSourceModel:destinationModel: we will use the initWithContentsOfURL: because the class method will return only one, maybe the first, found mapping model in the bundle.

Now we've got the two mapping models which can be used in every pass of the loop and send the migrate method to the migration manager. That's it.

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];
} 

Notes

  • A mapping model ends in cdm in the bundle.

  • The destination store has to be provided and should not be the source store. You can after successful migration delete the old and rename the new one.

  • I did some changes to the data model after the creation of the mapping models, this resulted in some compatibility errors, which I could only solve with recreating the mapping models.

月亮坠入山谷 2024-11-14 10:28:52

这些问题是相关的:

在 iPhone 上迁移大型 CoreData 数据存储时出现内存问题

多次传递iOS 中的核心数据迁移

引用第一个链接:

官方对此有讨论
“多次传递”中的文档
部分,但是看起来像他们的
建议的方法是划分
您按实体类型进行的迁移,即
制作多个映射模型,每个模型
迁移实体的子集
完整数据模型中的类型。

These questions are related:

Memory issues migrating large CoreData datastores on iPhone

Multiple Pass Core Data Migration In Chunks With iOS

To quote the first link:

This is discussed in the official
documentation in the "Multiple Passes"
section, however it looks like their
suggested approach is to divide up
your migration by entity type, i.e.
make multiple mapping models, each of
which migrate a subset of the entity
types from the complete data model.

本宫微胖 2024-11-14 10:28:52

假设您的数据库模式有 5 个实体,例如人员、学生、课程、班级和注册,以使用标准类型的示例,其中学生子类人员,班级实现课程,注册连接班级和学生。如果您对所有这些表定义进行了更改,则必须从基类开始,然后逐步向上。因此,您不能从转换注册开始,因为每个注册记录都取决于那里的班级和学生。因此,您将首先仅迁移 Person 表,将现有行复制到新表中,并填写其中的任何新字段(如果可能)并丢弃已删除的列。每次迁移都在自动释放池内进行,这样一旦完成,您的内存就会重新开始。

完成 Person 表后,您可以转换学生表。然后跳到课程,然后跳到班级,最后跳到注册表。

另一个考虑因素是记录的数量,如果像 Person 有一千行,你就必须每隔 100 行左右执行一次相当于发布的 NSManagedObject,这就是告诉托管对象上下文 [mocfreshObject:ob mergeChanges:不];
还将过时数据计时器设置得较低,以便经常刷新内存。

Suppose your database schema has 5 entities, e.g. person, student, course, class, and registration to use the standard kind of example, where student subclasses person, class implements course, and registration joins class and student. If you have made changes to all these table definitions, you have to start at the base classes, and work your way up. So, you cannot start with converting registrations, because each registration record depends on having class and students there. So, you would start with migrating only the Person table, copying existing rows into the new table, and filling in whatever new fields are there (if possible) and discarding the removed columns. Do each migration inside an autorelease pool, so that once it is done, your memory is back to start.

Once the Person table is done, then you can convert the student table over. Then hop over to Course and then Class, and the finally the Registration table.

The other consideration is the number of records, if like Person had a thousand rows, you would have to, every 100 or so, execute the NSManagedObject equivalent of a release, which is to tell the managed object context [moc refreshObject:ob mergeChanges:NO];
Also set your stale data timer way low, so that memory is flushed often.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文