自动创建Mapping
如果模型的改变很大或者不支持轻量级数据迁移的条件,则我们需要进行自定义迁移。
使用映射模型 适用于更加复杂的数据的迁移
NSMappingModel
类似于数据模型
NSEntityMapping
告知迁移过程如何在目标数据存储中处理源实体的映射。
映射类型决定了如何处理目标数据存储中的特定实体。映射类型有添加 移除 复制 变换。
- 添加映射:一个目标中的新实体添加到目标数据存储中
- 移除映射:实体不存在目标映射中,只存在于源中。
- 复制映射:将源对象完全相同地复制到目标
- 变换映射: 实体存在于源和目标中并且映射应该以某种方式将源变换到目标。
NSPropertyMapping
告知迁移过程如何将源属性映射到目标属性。
在从源数据存储到目标数据存储移动数据是可以提供一个值表达式来转换值。
开始一个自定义的迁移方式
1 新建一个mapModel
- 确保Data Model 处于选中状态
- File > New > File> iOS > Core Data > Mapping Model , 并点击Next 按钮
- 将旧版本的设置为Source Data Model 并点击Next 按钮 ,将新建的设置为Target Data Model, 并点击Next 按钮
- 保存mappingModel
2 代码的实现
首先要判断本地数据库文件和当前使用数据模型的结构是否相同。
如果不相同的话进行迁移。
1 在打开本地数据库的时候就要进行判断
// 判断是否需要迁移 是否已经迁移过了
- (BOOL)isHaveMigration
{
// 原来的数据库路径 不存在直接进行返回
NSString *docStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *sqlPath = [docStr stringByAppendingPathComponent:@"student.sqlite"];
NSURL *sqlURL = [NSURL fileURLWithPath:sqlPath];
if (![[NSFileManager defaultManager] fileExistsAtPath:sqlPath]) {
return NO;
}
// 比较存在模型数据的元数据
NSError *error = nil;
// 数据库的数据结构
NSDictionary *sourceMetaData = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:sqlURL options:nil error:&error];
// 比较数据库的数据 和目标数据是否相同 如果相同 不需要迁移操作
NSManagedObjectModel *destinationModel = _coord.managedObjectModel;
if ([destinationModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetaData]) {
return NO;
}
return YES;
}
2 如果需要迁移 则进行迁移操作
- (BOOL)startMigration
{
BOOL success = NO;
NSError *error = nil;
// 旧的数据库路径
NSString *docStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *sqlPath = [docStr stringByAppendingPathComponent:@"student.sqlite"];
NSURL *sqlURL = [NSURL fileURLWithPath:sqlPath];
// 原来的数据模型信息
NSDictionary *sourceMetaData = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:sqlURL options:nil error:&error];
// 原来的数据模型
NSManagedObjectModel *soureceModel = [NSManagedObjectModel mergedModelFromBundles:nil forStoreMetadata:sourceMetaData];
// 当前的数据模型信息
NSManagedObjectModel *destinationModel = _coord.managedObjectModel;
// 加载数据迁移的模型 mappingModel
NSMappingModel *mapModel = [NSMappingModel mappingModelFromBundles:nil forSourceModel:soureceModel destinationModel:destinationModel];
if (mapModel) {
// 迁移管理器
NSMigrationManager *mgr = [[NSMigrationManager alloc]initWithSourceModel:soureceModel destinationModel:destinationModel];
// 监听的数据迁移的进度
// 监听的进度可以用来给用户进行展示 例如说QQ新版本更新完之后打开之后,有一个进度条会更新数据,应该就是这个操作
[mgr addObserver:self
forKeyPath:@"migrationProgress"
options:NSKeyValueObservingOptionNew
context:NULL];
// 先把模型存储到临时的sqlite 迁移完成再进行替换操作
// 目的路径
NSString *destPath = [docStr stringByAppendingPathComponent:@"temp2.sqlite"];
NSURL *destURL = [NSURL fileURLWithPath:destPath];
success = [mgr migrateStoreFromURL:sqlURL type:NSSQLiteStoreType options:nil withMappingModel:mapModel toDestinationURL:destURL destinationType:NSSQLiteStoreType destinationOptions:nil error:&error];
if (success) {
// 证明数据迁移成功
[self replaceStore:sqlURL withStore:destURL];
}else{
// 迁移数据失败
NSLog(@"%@",error.description);
}
}
return success;
}
// 响应监听的方法
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:@"migrationProgress"]) {
dispatch_async(dispatch_get_main_queue(), ^{
float progress =
[[change objectForKey:NSKeyValueChangeNewKey] floatValue];
int percentage = progress * 100;
NSString *string =
[NSString stringWithFormat:@"Migration Progress: %i%%",
percentage];
NSLog(@"%@",string);
});
}
}
// 将旧的移除新的放置在旧的上面
-(BOOL)replaceStore:(NSURL *)old withStore:(NSURL *)new
{
BOOL success = NO;
NSError *error = nil;
if ([[NSFileManager defaultManager] removeItemAtURL:old error:&error]) {
error = nil;
if ([[NSFileManager defaultManager] moveItemAtURL:new toURL:old error:&error]) {
success = YES;
}
}
return success;
}