iCloud 核心数据迁移

Posted

技术标签:

【中文标题】iCloud 核心数据迁移【英文标题】:iCloud Core Data Migration 【发布时间】:2013-10-17 16:43:24 【问题描述】:

我正在开发一个仅限 ios 7 的应用程序,而且我对 iCloud 和 Core 数据还很陌生。 我的假设是,如果可能,用户总是希望使用 iCloud 进行备份。这就是为什么我按如下方式设置我的持久性存储:

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator 

 if (__persistentStoreCoordinator != nil) 

      return __persistentStoreCoordinator;

 

 NSURL *storeURL = [[[self applicationDocumentsDirectory]

                                URLByAppendingPathComponent:storeNameWithoutFileExtension isDirectory:NO]

                               URLByAppendingPathExtension:@"sqlite"];

 NSError *error = nil;

 __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];



 [[NSNotificationCenter defaultCenter] addObserver:self

                                                    selector:@selector(persistentStoreDidImportUbiquitiousContentChanges:)

                                                         name:NSPersistentStoreDidImportUbiquitousContentChangesNotification

                                                      object:__persistentStoreCoordinator];



 [[NSNotificationCenter defaultCenter] addObserver:self

                                                    selector:@selector(storesWillChange:)

                                                         name:NSPersistentStoreCoordinatorStoresWillChangeNotification

                                                      object:__persistentStoreCoordinator];



 [[NSNotificationCenter defaultCenter] addObserver:self

                                                    selector:@selector(storesDidChange:)

                                                         name:NSPersistentStoreCoordinatorStoresDidChangeNotification

                                                      object:__persistentStoreCoordinator];





 NSDictionary *options = @ NSInferMappingModelAutomaticallyOption : @YES,

                                  NSPersistentStoreUbiquitousContentNameKey : storeNameWithoutFileExtension, ;

// NSPersistentStoreRebuildFromUbiquitousContentOption

// NSMigratePersistentStoresAutomaticallyOption: @YES,

// NSMigratePersistentStoresAutomaticallyOption : @YES,



 if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType

                                                             configuration:nil

                                                                         URL:storeURL

                                                                    options:options

                                                                       error:&error])

 

      NSLog(@"Unresolved error %@, %@", error, [error userInfo]);

      abort();

 



 return __persistentStoreCoordinator;


通过此设置,即使用户未登录 iCloud(使用“本地”文件夹),我也总是会立即开始使用 Documents/CoreDataUbiquitySupport 下的文件夹。

如您所见,我正在注册通知。

为什么在 NSPersistentStoreCoordinatorStoresWillChangeNotification 和 NSPersistentStoreCoordinatorStoresDidChangeNotification 的 NSNotifications 中的键 NSAddedPersistentStoresKey 和 NSRemovedPersistentStoresKey 的数组中添加和删除的持久性存储总是相同的,例如为添加的案例帐户? 人们会期望通知包含已删除的持久性存储和新添加的。所以他们不应该是一样的。但不幸的是,它们总是一样的。

当前的行为是,如果我切换帐户或注销帐户或登录到新帐户,应用程序总是从关联的商店开始,如果以前没有,则使用新的商店。我想要的行为是应用程序始终将数据迁移到新商店。例如:用户未登录 iCloud 并开始使用我的应用程序。他创建了保存在 Documents/CoreDataUbiquitySupport 中的本地数据。现在他打开了 iCloud。现在我希望将本地数据迁移到云端。目前这没有发生。 我必须采用哪种方法来实现迁移,我该如何实现呢?我可以使用“migratePersistentStore”方法吗?我尝试在 NSPersistentStoreCoordinatorStoresDidChangeNotification 中使用以下代码,但无济于事:

 - (void)storesDidChange:(NSNotification *)n 

 // refresh user interface
 switch ([[n.userInfo objectForKey:NSPersistentStoreUbiquitousTransitionTypeKey] integerValue]) 

      case NSPersistentStoreUbiquitousTransitionTypeAccountAdded : 

           NSLog(@"NSPersistentStoreUbiquitousTransitionTypeAccountAdded migrate");


           NSError *error = nil;

           NSURL *storeURL = ((NSPersistentStore *)asdf[0]).URL;

           NSDictionary *options = @ NSMigratePersistentStoresAutomaticallyOption : @YES,

                                            NSInferMappingModelAutomaticallyOption : @YES,

                                            NSPersistentStoreUbiquitousContentNameKey : storeNameWithoutFileExtension, ;

           NSPersistentStore *asdfasdf = [__persistentStoreCoordinator migratePersistentStore:self.oldStore toURL:storeURL options:options withType:NSSQLiteStoreType error:&error];
      

      break;

      case NSPersistentStoreUbiquitousTransitionTypeAccountRemoved :

           NSLog(@"NSPersistentStoreUbiquitousTransitionTypeAccountRemoved");



           break;

      case NSPersistentStoreUbiquitousTransitionTypeContentRemoved : 

           NSLog(@"NSPersistentStoreUbiquitousTransitionTypeContentRemoved migrate");

           NSError *error = nil;

           NSURL *storeURL = ((NSPersistentStore *)asdf[0]).URL;

           NSDictionary *options = @ NSMigratePersistentStoresAutomaticallyOption : @YES,

                                            NSInferMappingModelAutomaticallyOption : @YES,

                                            NSPersistentStoreUbiquitousContentNameKey : storeNameWithoutFileExtension, ;

           NSPersistentStore *asdfasdf = [__persistentStoreCoordinator migratePersistentStore:self.oldStore toURL:storeURL options:options withType:NSSQLiteStoreType error:&error];

      

      break;

      case NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted :

           NSLog(@"NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted");

           NSLog(@"initial import");



           break;

      default :

           break;

 


变量 oldStore 是在 NSPersistentStoreCoordinatorStoresWillChangeNotification 中设置的,因为我不能依赖 NSNotification 及其键 NSAddedPersistentStoresKey 和 NSRemovedPersistentStoresKey,因为如前所述,两个数组始终包含与前面描述的相同的存储。

我将在哪种方法/什么时候实现重复数据删除代码?我可以在其中一个通知中这样做吗?

对于哪个用例,我必须使用 NSMigratePersistentStoresAutomaticallyOption?该选项究竟是如何工作的?

我在玩我的应用程序时看到的一个奇怪行为是,如果用户没有登录到 iCloud。当我将我的应用程序带到后台并退出 iCloud 并返回我的应用程序时,我得到的 NSNotifications 的类型是 NSPersistentStoreUbiquitousTransitionTypeContentRemoved。我希望 NSPersistentStoreUbiquitousTransitionTypeAccountRemoved。如果我随后将应用程序置于后台并再次备份,我将获得类型为 NSPersistentStoreUbiquitousTransitionTypeAccountRemoved 的 NSNotifications。但不幸的是,当用户未登录到 iCloud 并且应用程序正在运行并且用户将应用程序带到后台然后回到前台并且我得到类型为 NSPersistentStoreUbiquitousTransitionTypeAccountRemoved 的 NSNotifications 时,NSPersistentStoreCoordinatorStoresWillChangeNotification 和 NSPersistentStoreCoordinatorStoresDidChangeNotification 总是为用例触发。这总是发生。 - 为什么?

为了使我使用的代码完整,这里是我的 NSPersistentStoreCoordinatorStoresWillChangeNotification 和 NSPersistentStoreDidImportUbiquitousContentChangesNotification 的代码以及其他几个函数:

- (void)storesWillChange:(NSNotification *)n 

 NSLog(@"storesWillChange");

 NSArray *pStores = self.persistentStoreCoordinator.persistentStores;
 NSManagedObjectContext *moc = [self managedObjectContext];



 switch ([[n.userInfo objectForKey:NSPersistentStoreUbiquitousTransitionTypeKey] integerValue]) 

      case NSPersistentStoreUbiquitousTransitionTypeAccountAdded : 

           NSLog(@"NSPersistentStoreUbiquitousTransitionTypeAccountAdded");

           self.oldStore = pStores[0];

      

      break;

      case NSPersistentStoreUbiquitousTransitionTypeAccountRemoved :

           NSLog(@"NSPersistentStoreUbiquitousTransitionTypeAccountRemoved");



           break;

      case NSPersistentStoreUbiquitousTransitionTypeContentRemoved :

           NSLog(@"NSPersistentStoreUbiquitousTransitionTypeContentRemoved");

           self.oldStore = pStores[0];

           break;

      case NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted :

           NSLog(@"NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted");

           NSLog(@"initial import");



           break;

      default :

           break;

 

 [moc performBlockAndWait:^

       NSError *error = nil;

       if ([moc hasChanges]) 

            [moc save:&error];

       

       [moc reset];

  ];


 // reset user interface


 




- (void)persistentStoreDidImportUbiquitiousContentChanges:(NSNotification    *)changeNotification 

 NSLog(@"*** Incoming iCloud Data ***");

 NSManagedObjectContext *moc = [self managedObjectContext];



 [moc performBlock:^

       [moc mergeChangesFromContextDidSaveNotification:changeNotification];

  ];

 




 - (NSManagedObjectContext *)managedObjectContext 

 if (__managedObjectContext != nil) 

      return __managedObjectContext;

 



 NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];

 if (coordinator != nil) 

      __managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

 //          __managedObjectContext = [[NSManagedObjectContext alloc] init];

      [__managedObjectContext setPersistentStoreCoordinator:coordinator];

      [__managedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];

 

 return __managedObjectContext;

 


 - (NSManagedObjectModel *)managedObjectModel 

 if (__managedObjectModel != nil) 

      return __managedObjectModel;

 

 NSURL *modelURL = [[NSBundle mainBundle] URLForResource:dataModelName withExtension:@"momd"];

 __managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

 return __managedObjectModel;

 

所以基本上我想要完成的是用户拥有相同的数据,无论他使用哪个 iCloud 帐户,即使它是“本地”的。

任何帮助将不胜感激。提前谢谢!

【问题讨论】:

查看此链接以获取示例代码和视频,以在用户更改其 iCloud 设置时演示 iCloud 同步和存储迁移。 ossh.com.au/design-and-technology/software-development 【参考方案1】:

要将数据从一个 Core Data 存储迁移到另一个(无论是否使用 iCloud),请使用 NSPersistentStoreCoordinatormigratePersistentStore:toURL:options:withType:error: 方法。

但是,确保您的数据在所有 iCloud 帐户之间始终相同(已迁移)是:

可能很难(至少我不知道你会怎么做)。 可能不是您的用户想要的。每个 iCloud 帐户都有自己的数据是有意义的。

从 iOS 7 开始,iCloud 核心数据得到了显着改进并大大简化(针对开发人员)。其中一项简化是 Core Data 现在透明地处理 iCloud 帐户更改和本地后备存储,如果您打开一个无处不在的存储但用户未登录到 iCloud,则会创建一个“本地”存储。

不幸的是,这个“本地”商店被视为(虚拟)iCloud 帐户,因此从它迁移到另一个(真实)iCloud 帐户是很棘手的。因此,最好在您的应用程序中设置“使用 iCloud:是/否”选项。如果用户选择“否”,则创建一个真正的本地(未启用 iCloud)存储(存储在 Documents 文件夹中)。现在,当用户在您的应用中更改 iCloud 选项时,您可以选择将此本地存储迁移到启用 iCloud 的存储(使用migratePersistentStore:toURL:options:withType:error:),反之亦然,如果用户从 iCloud 切换到本地。

你问的另一个问题是:

对于哪个用例我必须使用 NSMigratePersistentStoresAutomaticallyOption?该选项究竟是如何工作的?

NSMigratePersistentStoresAutomaticallyOptionNSInferMappingModelAutomaticallyOption 选项用于将商店迁移到更新的数据模型(例如,如果您向实体添加字段),与从一个商店迁移无关给另一个。它们与您要在此处解决的问题无关。

【讨论】:

当用户禁用 iCloud 时,拥有真正的本地商店与“虚拟”iCloud 帐户有什么好处? @LocPham 的好处是,无论是否启用了 iCloud,您的应用程序都可以轻松访问这个非 iCloud 本地存储,从而允许用户在选择时从/向它迁移数据。如果用户使用真实帐户启用了 iCloud,则无法从您的应用程序访问“虚拟”iCloud 帐户存储。这是因为它被视为属于真实的 iCloud 帐户,并且您的应用无权访问其他 iCloud 帐户的 iCloud 存储。【参考方案2】:

我建议不要在 iOS6 中使用 iCloud+CoreData。有很多错误。阅读:

http://www.theverge.com/2013/3/26/4148628/why-doesnt-icloud-just-work

http://www.zdnet.com/developers-give-apple-an-icloud-ultimatum-fix-it-by-june-7000013495/

编辑

在 iOS7 中似乎有所改进,但开发人员表示在更改模型版本时会出现问题。 :(

【讨论】:

那是在 iOS7 之前。在 iOS7 中,iCloud 同步效果很好。 这对于iOS7来说确实已经过时了 iCloud 同步在 iOS 7 中仍然经常失败,并且应该尽可能避免。我现在大部分时间都在解决核心数据 iCloud 问题,而不是处理新功能和/或应用程序,这仍然适用于 iOS 7。 @ChristopherKing 同意,目前我在更改模型版本后遇到问题。

以上是关于iCloud 核心数据迁移的主要内容,如果未能解决你的问题,请参考以下文章

iOS:将现有核心数据数据库迁移到 iCloud

如何将现有核心数据 iOS 7 应用程序的数据迁移到 iCloud?

将 PersistentStoreCoordinator 从本地迁移到 iCloud 时数据重复

iCloud 和轻量级迁移

将 Core Data 应用程序迁移到 iCloud

iCloud、Core Data、迁移和模型映射