我的iPhone应用程序需要迁移其核心数据存储,并且某些数据库非常大。苹果的文档建议使用“多次通过”来迁移数据以减少内存使用。但是,文档非常有限,并且不能很好地说明如何实际执行此操作。有人可以给我指出一个好的例子,还是详细说明如何真正实现这一目标的过程?
我的iPhone应用程序需要迁移其核心数据存储,并且某些数据库非常大。苹果的文档建议使用“多次通过”来迁移数据以减少内存使用。但是,文档非常有限,并且不能很好地说明如何实际执行此操作。有人可以给我指出一个好的例子,还是详细说明如何真正实现这一目标的过程?
Answers:
我已经弄清了Apple在其文档中的提示。实际上,这很容易,但要走很长的路要走。我将通过一个示例来说明该解释。初始情况是这样的:
这是使用“带有核心数据存储的基于导航的应用程序”模板创建项目时获得的模型。我编译了它,并在for循环的帮助下进行了一些重创,以创建大约2k个条目,每个条目都有一些不同的值。在那里,我们以NSDate值进行了2.000个事件。
现在,我们添加数据模型的第二个版本,如下所示:
区别在于:事件实体不见了,我们有了两个新实体。一个将时间戳记存储为a double
,第二个将日期存储为NSString
。
目标是将所有版本1事件转移到两个新实体,并在迁移过程中转换值。这将导致值的两倍,每个值在单独的实体中作为不同的类型。
要进行迁移,我们选择手动迁移,而映射模型就是这样做。这也是您问题答案的第一部分。我们将分两步进行迁移,因为迁移2k条目需要花费很长时间,并且我们希望保持较低的内存占用。
您甚至可以继续拆分这些映射模型,以仅迁移实体范围。假设我们有100万条记录,这可能会使整个过程崩溃。使用Filter谓词可以缩小获取的实体的范围。
我们创建第一个映射模型,如下所示:
1.新建文件->资源->映射模型
2.选择一个名称,我选择了StepOne
3.设置源和目标数据模型
多遍迁移不需要自定义实体迁移策略,但是我们会这样做以获得本示例的更多细节。因此,我们向实体添加了自定义策略。这始终是的子类NSEntityMigrationPolicy
。
该策略类实现了一些使迁移发生的方法。但是,在这种情况下很简单,因此我们只需要实现一种方法: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。
最后,我们需要触发迁移。我现在暂时跳过样板代码。如果您需要,我会在这里发布。可以在“定制迁移过程”中找到它,它只是前两个代码示例的合并。如下第三和最后一部分将被修改:除了使用的类方法的NSMappingModel
类mappingModelFromBundles: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
捆绑包结尾。
必须提供目标存储,而不能将其作为源存储。成功迁移后,您可以删除旧的并重命名新的。
创建映射模型后,我对数据模型进行了一些更改,这导致了一些兼容性错误,我只能通过重新创建映射模型来解决。
这些问题相关:
在iPhone上迁移大型CoreData数据存储时出现内存问题
引用第一个链接:
官方文档的“多次通过”部分对此进行了讨论,但是看起来他们建议的方法是按实体类型划分迁移,即制作多个映射模型,每个映射模型都从实体模型中迁移实体类型的子集。完整的数据模型。
假设您的数据库模式有5个实体,例如人,学生,课程,班级和注册,以使用标准的示例类型,其中学生将人细分为人,班级实施课程,并且注册将班级和学生连接在一起。如果对所有这些表定义进行了更改,则必须从基类开始,然后逐步提高。因此,您不能从转换注册开始,因为每个注册记录都取决于那里的班级和学生。因此,您将从仅迁移Person表开始,将现有行复制到新表中,然后填写那里的任何新字段(如果可能)并丢弃已删除的列。在自动释放池中进行每次迁移,以便完成迁移后,便可以重新开始使用内存。
完成“人”表后,您可以将“学生”表转换过来。然后跳到“课程”,然后是“班级”,最后是“注册”表。
另一个要考虑的是记录的数量,如果像Person那样具有一千行,那么您将必须每100条记录执行一次与发行版等效的NSManagedObject,这将告诉托管对象上下文[moc refreshObject:ob mergeChanges:没有]; 同时将过时的数据计时器设置为较低,以便经常刷新内存。