《CoreData》系列(二)
发表于|更新于
|阅读量:
CoreData数据迁移以及版本升级
1 概述
为什么要有数据迁移?
由于CoreData可视化的特殊性,那么当数据模型发生变化时,相应的sqlite数据库的表由于不知道model发生了变化,表结构必须相应的做出调整,否则会导致程序Crash,CoreData的解决方案是通过创建新的sqlite表,然后将旧的数据迁移到新表上得方案来处理。下面分别介绍三种数据迁移的方式,并详细说明三种迁移方式的应用场景和注意事项。
1.轻量级的数据迁移方式
2.默认的迁移方式
3.使用迁移管理器
1.1 轻量级的数据迁移方式
轻量级的数据迁移,也就是说,并不需要程序员做很多事情就可以完成数据的迁移,是由系统默认进行的数据迁移。
那么如何进行轻量级的数据迁移呢,当model的表字段发生变化,且应用程序已经发布过版本时,此时千万不能单单修改原model来达到修改model的目的,如果这样做的话,程序会crash。正确的做法是,
1.新建一个model,并将model命名为model2,并将model2设置为当前model。
2.修改NSPersistentStoreCoordinator加载缓存区的配置。具体如下
1 2 3 4 5 6 7 8 9
| NSDictionary *option = @{NSMigratePersistentStoresAutomaticallyOption:@(YES), NSInferMappingModelAutomaticallyOption:@(YES), }; _store = [_coordinate addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self storeUrl] options:option error:&error];
|
tips:使用iCloud开发程序的app,只能使用这种迁移方式。
1.2 默认的迁移方式
正常情况下,使用轻量级的数据迁移已经足够了,但是如果由于开发需要,需要将某个Entity下面的某个Attribute迁移到新的Entity下的某个Attribute,那么轻量级的迁移方式就不能够满足需求,这个时候就需要使用默认的迁移方式来进行数据迁移。这里以一个例子代码来详细阐述如何进行默认的迁移

现在要将Model2里面的Measurement下面的name迁移到Account里面的下面的xyz属性下。
1.根据model2来创建一个新model,并命名为model3,然后将model3设置为currentmodel。
2.添加新的entity,并命名为Account,添加attribute xyz。
3.删除model2里面的Measurement,根据model3创建NSManagerObect的子类Account。
4.以model2为soureModel,model3为destinationModel添加一个MappingModel
5.按照下图所示设置映射model即可
6.最后记得将NSInferMappingModelAutomaticallyOption设置为Yes(coredata会优先读取映射model,如果没有就会自己推断),至此,默认的迁移方式就算是搞定了。


1.3 迁移管理器
简单概述下何为迁移管理器,迁移管理器,就是不再使用系统的NSPersistentCoordinator进行数据迁移,而是使用NSMigrationManager进行数据缓存区的迁移。并配合一个数据迁移视图控制器提供优雅的迁移等待界面。等待界面如下,是不是感觉很丑呢,哈哈。那么使用迁移管理器的好处又是什么呢?可以实现更加精细化的数据操作,此外还能向用户报告迁移进度。有这俩点,还不够我们去研究下它么?Let’s go!

准备工作
何时启用迁移管理器,即迁移的时机?
迁移工作如何进行?
迁移完成如何善后?
下面对上面的问题一一来做解答
迁移的时机,迁移工作需要在载入数据库的时候进行,即上节所讲的 loadStore:的时候进行,但是呢?还需要做一些判断工作。具体代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| - (void)loadStore { if (debug) { NSLog(@"Running %@ ,'%@'",[self class], NSStringFromSelector(_cmd)); } if (_store) { return; } BOOL useMigrateManager = MigrationMode; if (useMigrateManager && [self isMigrationNecessaryForStore:[self storeUrl]]) { [self performBackgroundManagedMigrationForStore:[self storeUrl]]; }else{ NSError *error; NSDictionary *option = @{NSMigratePersistentStoresAutomaticallyOption:@(YES), NSInferMappingModelAutomaticallyOption:@(YES), NSSQLitePragmasOption:@{@"journal_mode":@"DELETE"}}; _store = [_coordinate addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self storeUrl] options:option error:&error]; if (!_store) { if (debug) { NSLog(@"failed load store,error = %@",error); abort(); } } else{ NSLog(@"successfully add store : %@",_store); } } }
|
其中有开关,用来控制是否使用迁移管理器,以及系统是否需要进行迁移的判断。系统是否需要迁移的判断代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| - (BOOL)isMigrationNecessaryForStore:(NSURL *)storeUrl { if (debug) { NSLog(@"Running %@ '%@'",[self class],NSStringFromSelector(_cmd)); } if (![[NSFileManager defaultManager]fileExistsAtPath:[self storeUrl].path isDirectory:nil]) { if (debug) { NSLog(@"Skipped Migration, source database missing"); } return NO; } NSError *error = nil; NSDictionary *sourceMetaData = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:storeUrl error:&error]; NSManagedObjectModel *destinationModel = _coordinate.managedObjectModel; if ([destinationModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetaData]) { if (debug) { NSLog(@"Skipped Migration, source database is already compatible"); return NO; } } return YES; }
|
迁移工作如何进行,众所周知,迁移工作是一项比较耗时间的工作,尤其是在数据库比较大的情况下,那么肯定不能放在前台进行,必须放在后台进行,前台展示加载进度,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| - (void)performBackgroundManagedMigrationForStore:(NSURL *)store { if (debug) { NSLog(@"Running %@ '%@'",[self class],NSStringFromSelector(_cmd)); } UIStoryboard *sb = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; self.migrationVC = [sb instantiateViewControllerWithIdentifier:@"migration"]; UIApplication *app = [UIApplication sharedApplication]; UINavigationController *navigationCtl = (UINavigationController *)[app keyWindow].rootViewController; [navigationCtl presentViewController:self.migrationVC animated:YES completion:nil]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ BOOL done = [self migrateStore:[self storeUrl]]; if (done) { dispatch_async(dispatch_get_main_queue(), ^{ NSError *error = nil; NSDictionary *configuration = @{NSMigratePersistentStoresAutomaticallyOption:@(YES), NSInferMappingModelAutomaticallyOption:@(YES), NSSQLitePragmasOption:@{@"journal_mode":@"DELETE"}}; _store = [_coordinate addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self storeUrl] options:configuration error:&error]; if (_store) { if (debug) { NSLog(@"success create store"); } }else { if (debug) { NSLog(@"failed, error = %@",error); } abort(); } [self.migrationVC dismissViewControllerAnimated:YES completion:nil]; self.migrationVC = nil; }); } }); }
|
接下来是,真正的迁移过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| - (BOOL)migrateStore:(NSURL *)store { if (debug) { NSLog(@"Running %@ '%@'",[self class],NSStringFromSelector(_cmd)); } NSDictionary *sourceMeta = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:store error:nil]; NSManagedObjectModel *sourceModel = [NSManagedObjectModel mergedModelFromBundles:nil forStoreMetadata:sourceMeta]; NSManagedObjectModel *destinationModel = _model; NSMappingModel *mappingModel = [NSMappingModel mappingModelFromBundles:nil forSourceModel:sourceModel destinationModel:destinationModel]; if (mappingModel) { NSError *error = nil; NSMigrationManager *migrationManager = [[NSMigrationManager alloc]initWithSourceModel:sourceModel destinationModel:destinationModel]; [migrationManager addObserver:self forKeyPath:@"migrationProgress" options:NSKeyValueObservingOptionNew context:nil]; NSURL *destinationStore = [[self applicationStoreDirectory]URLByAppendingPathComponent:@"temp.sqlite"]; BOOL success = NO; success = [migrationManager migrateStoreFromURL:store type:NSSQLiteStoreType options:nil withMappingModel:mappingModel toDestinationURL:destinationStore destinationType:NSSQLiteStoreType destinationOptions:nil error:&error]; if (success) { if (debug) { NSLog(@"Migration Successfully!"); } if ([self replaceStore:store withStore:destinationStore]) { [migrationManager removeObserver:self forKeyPath:@"migrationProgress" context:NULL]; [[NSNotificationCenter defaultCenter]postNotificationName:someThingChangedNotification object:nil]; } }else{ if (debug) { NSLog(@"Migration Failed"); } } }else{ if (debug) { NSLog(@"Mapping model is NULL"); } } return YES; }
|
最后附上俩个辅助方法,用来观察迁移过程和替换数据库的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| - (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; }else { if (debug) { NSLog(@"failed move new store to old"); } } }else{ if (debug) { NSLog(@"failed remove old store"); } } return success; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(voidvoid *)context { if ([keyPath isEqualToString:@"migrationProgress"]) { dispatch_async(dispatch_get_main_queue(), ^{ float progress = [[change objectForKey:NSKeyValueChangeNewKey]floatValue]; self.migrationVC.progressView.progress = progress; int percenttage = progress * 100; NSString *string = [NSString stringWithFormat:@"Migration Progress %i%%",percenttage]; self.migrationVC.progressLabel.text = string; }); } }
|
至此,三种数据迁移的方式,都已叙述完毕。
[2 小结](#1)
三种迁移方式,各有各的好处,轻量级的迁移可以配套icloud实现云端存储,默认的数据迁移,支持将属性级别的数据进行任意迁移。迁移管理器,可以管理文件存储路径,并能够报告迁移进度,我们在开发过程中,应该按照自己的需求合理选择迁移方式,下一小节结合NSFetchedResultController进行数据的实际应用。