我将尝试回答我自己的问题,部分是为了整理我的想法,部分是为了回复@DuncanGroenewald。
1) 这种方法是否有任何缺点或边界情况,可能
乍一看不明显?
是的。本地和 iCloud 帐户存储由 Core Data 管理,可以随时删除。
实际上,我认为本地帐户存储永远不会被删除,因为它无法从 iCloud 重新创建。
关于 iCloud 帐户存储,我可以看到它们可能被删除的两种情况:a)在用户关闭 iCloud 后释放空间或 b)因为用户通过选择 设置 > iCloud > 全部删除来请求它.
如果用户要求,那么您可能会争辩说数据迁移不是问题。
如果要释放空间,那么是的,这是一个问题。但是,任何其他方法都存在同样的问题,因为当 iCloud 帐户存储被删除时,您的应用程序不会被唤醒。
2) 是 NSPersistentStoreCoordinatorStoresWillChangeNotification 和
NSPersistentStoreCoordinatorStoresDidChangeNotification 足以
检测所有可能的开到关和关到开的 iCloud 转换?
是的。它要求您始终使用NSPersistentStoreUbiquitousContentNameKey 创建持久存储,无论 iCloud 是打开还是关闭。像这样:
[self.managedObjectContext.persistentStoreCoordinator
addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:@{ NSPersistentStoreUbiquitousContentNameKey : @"someName" }
error:&error];
其实只听NSPersistentStoreCoordinatorStoresDidChangeNotification就够了(如下图)。这将在启动时添加商店或在执行期间更改商店时调用。
3) 你会做用户提示和合并
NSPersistentStoreCoordinatorStoresWillChangeNotification 和
NSPersistentStoreCoordinatorStoresDidChangeNotification,或收集所有
那些信息,等到店里改了?我问
因为这些通知似乎是在后台发送的,并且
阻止他们执行潜在的长时间操作可能不会
Core Data 的预期。
这就是我在NSPersistentStoreCoordinatorStoresDidChangeNotification 中的做法。
由于此通知在启动时和执行期间存储更改时发送,我们可以使用它来保存当前存储 url 和无处不在的身份令牌(如果有)。
然后我们检查我们是否处于开/关转换场景并相应地迁移数据。
为简洁起见,我没有包含任何 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进行。此逻辑将取决于您的模型以及重复和冲突策略。
我不知道这是否会产生不必要的副作用。显然,这可能需要相当长的时间,具体取决于持久存储的大小和数据。如果我发现任何问题,我会编辑答案。