通过多次传递进行核心数据迁移的示例或说明?


85

我的iPhone应用程序需要迁移其核心数据存储,并且某些数据库非常大。苹果的文档建议使用“多次通过”来迁移数据以减少内存使用。但是,文档非常有限,并且不能很好地说明如何实际执行此操作。有人可以给我指出一个好的例子,还是详细说明如何真正实现这一目标的过程?


您是否确实遇到了内存问题?您的迁移重量很轻,还是想使用NSMigrationManager?
尼克·韦弗

是的,GDB控制台显示存在内存警告,然后由于内存不足而使应用程序崩溃。我已经尝试了轻量级迁移和NSMigrationManager,但是现在我正在尝试使用NSMigrationManager。
杰森(Jason)

好的,您能详细介绍一下哪些变化吗?
尼克·韦弗

终于,我找到答案了。
尼克·韦弗

你好杰森,你能解决这个问题吗?
Yuchen Zhong'3

Answers:


174

我已经弄清了Apple在其文档中的提示。实际上,这很容易,但要走很长的路要走。我将通过一个示例来说明该解释。初始情况是这样的:

数据模型版本1

在此处输入图片说明 在此处输入图片说明

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

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

在此处输入图片说明

数据模型版本2

区别在于:事件实体不见了,我们有了两个新实体。一个将时间戳记存储为a double,第二个将日期存储为NSString

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

要进行迁移,我们选择手动迁移,而映射模型就是这样做。这也是您问题答案的第一部分。我们将分两步进行迁移,因为迁移2k条目需要花费很长时间,并且我们希望保持较低的内存占用。

您甚至可以继续拆分这些映射模型,以仅迁移实体范围。假设我们有100万条记录,这可能会使整个过程崩溃。使用Filter谓词可以缩小获取的实体的范围。

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

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

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转换为double的timeIntervalSince1970。

最后,我们需要触发迁移。我现在暂时跳过样板代码。如果您需要,我会在这里发布。可以在“定制迁移过程”中找到它,它只是前两个代码示例的合并。如下第三和最后一部分将被修改:除了使用的类方法的NSMappingModelmappingModelFromBundles:forSourceModel:destinationModel:,我们会使用initWithContentsOfURL:,因为该类方法将返回只有一个,也许是第一次,发现映射在捆绑模式。

现在,我们有了两个映射模型,它们可以在循环的每个过程中使用,并将迁移方法发送到迁移管理器。而已。

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
血腥的地狱很复杂。苹果在想什么?
aroth 2012年

7
我不知道,但是每当我认为核心数据是一个好主意时,我都会努力寻找一个更简单,更可维护的解决方案。
尼克·韦弗

5
谢谢!这是一个极好的答案。这看起来很复杂,但是一旦您学习了这些步骤,它并没有那么糟糕。最大的问题是文档无法像这样为您说明。
宾福德

2
这是自定义迁移过程的更新链接。自从写这篇文章以来,它已经发生了变化。 developer.apple.com/library/ios/documentation/Cocoa/Conceptual/...
user1021430

@NickWeaver您如何确定destinationStoreURL?您是在创建它还是在迁移过程中由核心数据系统创建了它?
dev gr 2014年

3

这些问题相关:

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

iOS多通道核心数据迁移

引用第一个链接:

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


1
感谢您的链接。问题是没有人真正详细解释过如何多次设置它。我应该如何设置多个映射模型以使其有效运行?
杰森

-5

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

完成“人”表后,您可以将“学生”表转换过来。然后跳到“课程”,然后是“班级”,最后是“注册”表。

另一个要考虑的是记录的数量,如果像Person那样具有一千行,那么您将必须每100条记录执行一次与发行版等效的NSManagedObject,这将告诉托管对象上下文[moc refreshObject:ob mergeChanges:没有]; 同时将过时的数据计时器设置为较低,以便经常刷新内存。


因此,您实质上是在建议使用新的核心数据架构(不是旧架构的一部分),并手动将数据复制到新架构吗?
杰森

-1不需要手动映射数据库。您可以使用轻量级迁移或显式MappingModels迁移已部署的数据库。
宾福德
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.