打开/关闭iCloud时迁移数据

时间:2014-02-20 00:02:31

标签: ios macos core-data icloud core-data-migration

本地帐户

关于核心数据和iCloud的WWDC 2013 207 session

  

您在应用程序的本地内部提供单个商店URL   沙盒然后我们创建一个不透明的容器,里面有一个条目   它适用于系统上的每个帐户,包括本地帐户   是我们的术语,指的是当没有iCloud帐户时会发生什么   系统。这是一个由Core Data管理的特殊商店   你不必做任何特别的事情,因为你的用户没有   一个iCloud帐户。

在iOS 7 / OS X 10.9中,带有iCloud的Core Data会自动使用本地帐户来处理iCloud关闭的情况。与后备存储(在iCloud开启但无法访问时使用)不同,当服务开启时,本地帐户将完全被iCloud帐户替换,而不进行任何合并。只有iCloud关闭时,才能访问本地帐户中的数据。这种情况发生在:

  • 没有iCloud帐户。
  • 有一个iCloud帐户,但“文档和数据”已被禁用。
  • 有一个iCloud帐户,但该应用已在“文档和数据”中禁用。

以上是我从实验中理解的内容。如果我错了,请纠正我。

数据消失时

本地帐户用户体验非常糟糕。如果您在关闭iCloud的情况下向应用添加数据然后将其打开,数据将“消失”,您可能会认为它已被删除。如果您在启用了iCloud的应用中添加数据,然后将其关闭,则数据也会“消失”。

我见过一些例子,试图通过向应用添加(更多)iCloud设置并管理他们自己的“本地”商店(不是iCloud提供的商店)来解决这个问题。这让我重复工作。

利用本地帐户进行数据迁移

这种做法怎么样?

  • 无论iCloud是打开还是关闭,始终使用Core Data和iCloud。
  • 当iCloud从off变为on时,询问用户是否要将本地帐户与iCloud帐户合并。如果是,请合并,删除重复项,优先考虑本地帐户并清空本地帐户。
  • 当iCloud从开启到关闭时,询问用户是否要将iCloud商店与本地帐户合并。如果是,请合并并删除重复项,优先考虑iCloud。

这类似于提醒所做的。但是,提醒会直接从iCloud设置向用户询问数据迁移,这是我们开发人员无法做到的事情。

问题

1)这种方法是否有任何缺点或边界情况乍一看可能不明显?也许我们不打算像这样使用iCloud生成的本地帐户。

2)NSPersistentStoreCoordinatorStoresWillChangeNotificationNSPersistentStoreCoordinatorStoresDidChangeNotification是否足以检测到iCloud过渡的所有可能开启和关闭?

3)您是否会在NSPersistentStoreCoordinatorStoresWillChangeNotificationNSPersistentStoreCoordinatorStoresDidChangeNotification之间进行用户提示和合并,或者收集其中的所有信息并等到商店更改?我问,因为这些通知似乎是在后台发送的,阻止它们执行可能很长的操作可能不是Core Data所期望的。

1 个答案:

答案 0 :(得分:3)

我将尝试回答我自己的问题,部分是为了组织我的想法,部分是为了回复@DuncanGroenewald。

  

1)这种方法是否有任何缺点或边界情况   乍一看不明显?

是。本地和iCloud帐户存储由Core Data管理,可以随时删除。

在实践中,我认为本地帐户商店不会被删除,因为它无法从iCloud重新创建。

关于iCloud帐户存储,我可以看到两种可能被删除的情况:a)在用户关闭iCloud后释放空间或b)因为用户通过选择设置>来请求它。 iCloud>全部删除

如果用户请求了,那么您可能会认为数据迁移不是问题。

如果是为了腾出空间,那么是的,这是一个问题。但是,任何其他方法都存在同样的问题,因为当删除iCloud帐户存储时,您的应用程序未被唤醒。

  

2)NSPersistentStoreCoordinatorStoresWillChangeNotification和   NSPersistentStoreCoordinatorStoresDidChangeNotification足以   检测所有可能的开启和关闭到iCloud过渡?

是。无论iCloud是打开还是关闭,它都要求您始终使用NSPersistentStoreUbiquitousContentNameKey创建持久性存储。像这样:

[self.managedObjectContext.persistentStoreCoordinator
 addPersistentStoreWithType:NSSQLiteStoreType
 configuration:nil
 URL:storeURL
 options:@{ NSPersistentStoreUbiquitousContentNameKey : @"someName" }
 error:&error];

事实上,只听NSPersistentStoreCoordinatorStoresDidChangeNotification就足够了(如下所示)。在启动时添加存储或在执行期间更改存储时将调用此方法。

  

3)你会做用户提示和合并吗?   NSPersistentStoreCoordinatorStoresWillChangeNotification和   NSPersistentStoreCoordinatorStoresDidChangeNotification,或全部收集   那些信息,等到商店改变了?我问   因为这些通知似乎是在后台发送的,并且   阻止他们执行可能很长的操作可能不是   Core Data期望的是什么。

这就是我在NSPersistentStoreCoordinatorStoresDidChangeNotification中的表现。

由于此通知在启动时和商店在执行期间发生更改时都会发送,因此我们可以使用它来保存当前商店网址和普遍存在的身份令牌(如果有)。

然后我们检查我们是否处于开/关转换场景并相应地迁移数据。

为了简洁起见,我没有包含任何UI代码,用户提示或错误处理。在进行任何迁移之前,您应该询问(或至少通知)用户。

- (void)storesDidChange:(NSNotification *)notification
{
    NSDictionary *userInfo = notification.userInfo;
    NSPersistentStoreUbiquitousTransitionType transitionType = [[userInfo objectForKey:NSPersistentStoreUbiquitousTransitionTypeKey] integerValue];
    NSPersistentStore *persistentStore = [userInfo[NSAddedPersistentStoresKey] firstObject];
    id<NSCoding> ubiquityIdentityToken = [NSFileManager defaultManager].ubiquityIdentityToken;

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    if (transitionType != NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted) { // We only care of cases if the store was added or removed
        NSData *previousArchivedUbiquityIdentityToken = [defaults objectForKey:HPDefaultsUbiquityIdentityTokenKey];
        if (previousArchivedUbiquityIdentityToken) { // Was using ubiquity store
            if (!ubiquityIdentityToken) { // Changed to local account
                NSString *urlString = [defaults objectForKey:HPDefaultsPersistentStoreURLKey];
                NSURL *previousPersistentStoreURL = [NSURL URLWithString:urlString];
                [self importPersistentStoreAtURL:previousPersistentStoreURL
                 isLocal:NO
                 intoPersistentStore:persistentStore];
            }
        } else { // Was using local account
            if (ubiquityIdentityToken) { // Changed to ubiquity store
                NSString *urlString = [defaults objectForKey:HPDefaultsPersistentStoreURLKey];
                NSURL *previousPersistentStoreURL = [NSURL URLWithString:urlString];
                [self importPersistentStoreAtURL:previousPersistentStoreURL 
                 isLocal:YES 
                 intoPersistentStore:persistentStore];
            }
        }
    }
    if (ubiquityIdentityToken) {
        NSData *archivedUbiquityIdentityToken = [NSKeyedArchiver archivedDataWithRootObject:ubiquityIdentityToken];
        [defaults setObject:archivedUbiquityIdentityToken forKey:HPModelManagerUbiquityIdentityTokenKey];
    } else {
        [defaults removeObjectForKey:HPModelManagerUbiquityIdentityTokenKey];
    }
    NSString *urlString = persistentStore.URL.absoluteString;
    [defaults setObject:urlString forKey:HPDefaultsPersistentStoreURLKey];
    dispatch_async(dispatch_get_main_queue(), ^{
        // Update UI
    });
}

然后:

- (void)importPersistentStoreAtURL:(NSURL*)importPersistentStoreURL 
    isLocal:(BOOL)isLocal 
    intoPersistentStore:(NSPersistentStore*)persistentStore 
{
    if (!isLocal) { 
        // Create a copy because we can't add an ubiquity store as a local store without removing the ubiquitous metadata first,
        // and we don't want to modify the original ubiquity store.
        importPersistentStoreURL = [self copyPersistentStoreAtURL:importPersistentStoreURL];
    }
    if (!importPersistentStoreURL) return;

    // You might want to use a different concurrency type, depending on how you handle migration and the concurrency type of your current context
    NSManagedObjectContext *importContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
    importContext.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
    NSPersistentStore *importStore = [importContext.persistentStoreCoordinator
            addPersistentStoreWithType:NSSQLiteStoreType
            configuration:nil
            URL:importPersistentStoreURL
            options:@{NSPersistentStoreRemoveUbiquitousMetadataOption : @(YES)}
            error:nil];

    [self importContext:importContext intoContext:_managedObjectContext];
    if (!isLocal) {
        [[NSFileManager defaultManager] removeItemAtURL:importPersistentStoreURL error:nil];
    }
}

数据迁移在importContext:intoContext中执行。此逻辑将取决于您的模型以及重复和冲突策略。

我无法判断这是否会产生不良副作用。显然,这可能需要一段时间,具体取决于持久性存储的大小和数据。如果我发现任何问题,我会编辑答案。