将iCloud数据迁移到本地存储,并停止iCloud的响应



我非常接近完成iCloud和Core Data在我的应用程序中的实现。这是一个iOS 7应用程序。

我在应用程序中为用户提供一个选项,以UISwitch的形式使用iCloud打开或关闭。应用程序启动并询问用户是否要使用iCloud,稍后可以在应用程序中更改。

我将数据从本地存储(如果存在)迁移到iCloud。我正在努力的一个方面是将数据从iCloud Store迁移到Local Store。

场景:-用户A的iPhone上有我的应用程序和一些数据-用户A将应用程序下载到iPad上,选择使用iCloud,但后来又决定不需要在iPad上同步。所以他们在应用程序中关闭了iPad iCloud同步功能。

在这一点上,我的理解是用户希望数据仍然在那里,但从幕后来看,它现在是"LOCAL"而不是"iCloud"基于数据,这意味着,来自其他设备的条目不会同步到这个设备,反之亦然

所以我正在努力实现这一点。

我可以将数据从iCloud迁移到本地存储,但问题是,因为iCloud仍然在设备上启用,下次应用程序运行时,我可以看到iCloud在应用程序中被禁用,但控制台仍然显示使用本地存储1,然后0,任何来自iPhone的数据同步到iPad,即使使用iCloud UISwitch非常明显关闭。

下面是执行迁移的主代码:

- (void)migrateiCloudStoreToLocalStore {
    NSLog(@"Migrate iCloudToLocalStore");
    NSPersistentStore *store = self.persistentStoreCoordinator.persistentStores.lastObject;
    //NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Envylope.sqlite"];
    NSURL *storeURL = [self.persistentStoreCoordinator.persistentStores.lastObject URL];
    NSLog(@"Current Store URL (before iCloud to Local migration): %@", [storeURL description]);
    NSDictionary *localStoreOptions = nil;
    localStoreOptions = @{ NSPersistentStoreRemoveUbiquitousMetadataOption : @YES,
                           NSMigratePersistentStoresAutomaticallyOption : @YES,
                           NSInferMappingModelAutomaticallyOption : @YES};
    NSPersistentStore *newStore = [self.persistentStoreCoordinator migratePersistentStore:store
                                                                                     toURL:storeURL
                                                                                   options:localStoreOptions
                                                                                  withType:NSSQLiteStoreType error:nil];
    [self reloadStore:newStore];
}
- (void)reloadStore:(NSPersistentStore *)store {
    NSLog(@"Reload Store");        
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Envylope.sqlite"];
    NSDictionary *localStoreOptions = nil;
    localStoreOptions = @{ NSPersistentStoreRemoveUbiquitousMetadataOption : @YES,
                           NSMigratePersistentStoresAutomaticallyOption : @YES,
                           NSInferMappingModelAutomaticallyOption : @YES};
    if (store) {
        [self.persistentStoreCoordinator removePersistentStore:store error:nil];
    }
    [self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                  configuration:nil
                                                            URL:storeURL
                                                        options:localStoreOptions
                                                          error:nil];
    storeURL = [self.persistentStoreCoordinator.persistentStores.lastObject URL];
    NSLog(@"Current Store URL (after iCloud to Local migration): %@", [storeURL description]);
    NSLog(@"Done reloading");
    [[NSUserDefaults standardUserDefaults]setBool:YES forKey:@"MigratedFromiCloudToLocal"];
    [[NSUserDefaults standardUserDefaults]synchronize];
}

当然,有一些冗余的代码在那里,但NSLog之前的iCloud到本地迁移显示:

 Current Store URL (before iCloud to Local migration): file:///var/mobile/Applications/62CAFCEE-450E-410D-9D56-4918DD70EC07/Documents/CoreDataUbiquitySupport/mobile~98457667-A467-4095-BB7F-EF36EB2B0FE1/EnvyCloud/CD08912E-C135-4056-9557-6B47C84ECEF9/store/Envylope.sqlite

迁移完成后的NSLog显示:

Current Store URL (after iCloud to Local migration): file:///var/mobile/Applications/62CAFCEE-450E-410D-9D56-4918DD70EC07/Documents/Envylope.sqlite

这清楚地显示数据已迁移到本地存储。然而,在控制台的这一点上,我不确定会发生什么,但理想情况下,这个过程应该具有与设备上没有启用iCloud相同的效果(如果我删除我的应用程序,删除我在设备上的iCloud帐户并运行应用程序,我得到了预期的-运行没有任何iCloud选项的应用程序)。这并没有发生,因为它继续推动iCloud更新。无需再次运行应用程序,在其他设备中添加的数据就会同步到这里。再次运行应用程序,使用本地存储1和0出现在控制台中。

因此,数据可能已经迁移到本地存储,但在应用程序中禁用iCloud似乎没有效果。

我真的很感激你对这个问题的看法。

更新:解决方案是下面两个答案的组合。如果有人想了解下一个场景,请参考这个问题:将iCloud Store迁移到本地商店,并确保每次应用启动时数据都在那里

这是一个两步的过程:就像@wottle建议的那样,从应用程序中删除所有三个观察者。一旦iCloud数据迁移到新的本地存储文件中,就可以执行此操作:

- (void)removeCloudObservers {
[[NSNotificationCenter defaultCenter]removeObserver:self name:NSPersistentStoreCoordinatorStoresWillChangeNotification object:self.managedObjectContext.persistentStoreCoordinator];
[[NSNotificationCenter defaultCenter]removeObserver:self name:NSPersistentStoreCoordinatorStoresDidChangeNotification object:self.managedObjectContext.persistentStoreCoordinator];
[[NSNotificationCenter defaultCenter]removeObserver:self name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:self.managedObjectContext.persistentStoreCoordinator];
}

接下来,如果你绝对肯定你想从iCloud中删除整个泛在容器(就像我们之前手动做的那样),你可以调用NSPersistentStoreCoordinator类上的一个单例方法(removeUbiquitousContentAndPersistentStoreAtURL)。抓取有问题的iCloud store文件(我将其命名为yourStore),然后使用如下命令:

- (void)removeCloudContainer:(NSPersistentStore *)yourStore {
NSError *error = nil;
if (![NSPersistentStoreCoordinator removeUbiquitousContentAndPersistentStoreAtURL:yourStore.URL options:yourStore.options error:&error]) {
    NSLog(@"That didn't work so well, and here's why: %@", error.localizedFailureReason);
}
}

这将删除整个容器,并且没有其他设备将能够访问iCloud数据。当iCloud store从协调器中移除并且应用程序已经在本地store上正常运行时,请执行此操作。

从UI的角度来看,让用户在所有设备上的所有数据都迁移到本地存储后做出这个选择,而不是将其集成到"关闭iCloud"例程中,这可能是一个好主意,只是因为它可能难以处理仍然期望iCloud存储在那里的同行。

看起来您正在正确迁移数据存储,但仍然收到iCloud修改的通知。在代码的某个地方,你要为iCloud的变化注册一个观察者,比如

NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
[[NSNotificationCenter defaultCenter] addObserver:self
                                     selector:@selector(updateKVStoreItems:)
                                         name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
                                       object:store];

当你切换到本地数据时,你应该删除观察者,这样当苹果端iCloud数据存储中的一些数据发生变化时,你将不再在该设备上收到通知(并且如果他们切换回iCloud,请确保添加观察者)。

[[NSNotificationCenter defaultCenter] removeObserver:self name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:nil];

请注意,如果您使用iCloud与核心数据存储,而不是简单的键值存储,您的通知可能会有所不同,如我所示。

最新更新