hpi*_*que 9 macos core-data core-data-migration ios icloud
关于Core Data和iCloud 的WWDC 2013 207会议:
您在应用程序的本地沙箱中为我们提供单个商店URL,然后我们为系统中的每个帐户创建一个不透明容器,其中包含一个条目,包括本地帐户,这是我们在没有iCloud帐户时会发生什么的术语在系统上.这是一个由Core Data管理的特殊商店,因此您无需执行任何特殊操作,因为您的用户没有iCloud帐户.
在iOS 7/OS X 10.9中,带有iCloud的Core Data将自动使用本地帐户来处理iCloud关闭的情况.与后备存储(在iCloud打开但无法访问时使用)不同,本地帐户将在服务启用时由iCloud帐户全部替换,而不进行任何合并.只有iCloud关闭时,才能访问本地帐户中的数据.这种情况发生在:
以上是我从实验中理解的.如果我错了,请纠正我.
按原样使用,本地帐户用户体验非常糟糕.如果您在关闭iCloud的情况下向应用添加数据然后将其打开,数据将"消失",您可能会认为它已被删除.如果您在启用了iCloud的应用程序中添加数据,然后将其关闭,则数据也将"消失".
我已经看到了一些例子,试图通过向应用添加(更多)iCloud设置并管理他们自己的"本地"商店(不是iCloud提供的商店)来解决这个问题.这让我重复工作.
这种方法怎么样?
这类似于Reminders所做的.但是,Reminders直接从iCloud设置向用户询问数据迁移,这是我们开发人员无法做到的.
1)这种方法是否有任何缺点或边界情况乍一看可能不明显?也许我们不打算像这样使用iCloud生成的本地帐户.
2)是否NSPersistentStoreCoordinatorStoresWillChangeNotification和NSPersistentStoreCoordinatorStoresDidChangeNotification足以检测出所有可能的上断和断到在iCloud上的转变?
3)你会做的用户提示和合并之间NSPersistentStoreCoordinatorStoresWillChangeNotification和NSPersistentStoreCoordinatorStoresDidChangeNotification,或聚集在这些所有的信息和等待商店改变了吗?我问,因为这些通知似乎是在后台发送的,阻止它们执行可能很长的操作可能不是Core Data所期望的.
我想你误解了207会议上的内容.
Core Data不会自动为您创建本地和iCloud存储,也不会在iCloud帐户关闭时同步数据.根据用户选择的内容,您必须使用NSPersistentStoreUbiquityNameKey选项(对于iCloud存储)或不使用它(对于本地存储)来创建存储.
由于首次安装应用程序时新应用程序数据和文档的默认安全设置为ON,因此您必须询问用户是否要使用iCloud.尝试使用Apple的Pages应用程序.
如果用户随后更改了首选项设置,则您的应用必须将商店迁移到iCloud或从iCloud迁移.
Core Data自动处理的部分是,如果您切换iCloud帐户(注销并使用其他帐户登录),则App将在登录此帐户时运行可能已创建的任何Core Data存储.
请参阅下面的脚本,其中明确指出当帐户消失时iCloud商店将被删除.它消失了,卡普特,一只死鹦鹉.因此,当您有机会保存时,只有更改日志保留在本地,以防将来再次使用该帐户.
您只需实现您的意愿更改处理程序并响应NSPersistentStoreCoordinator Stores Will Change,并在我们需要更改持久存储文件时自动通知您,因为系统上有新帐户.
当然,您可以调用NSManagedObjectContext save和NSManagedObjectContext重置.
现在,一旦你完成了,我们将从协调器中删除存储,就像异步设置过程一样,然后我们将再次发送NSPersistentStoreCoordinator Storage Did Change通知,就像异步设置一样,你可以开始使用你的像往常一样申请.
现在,让我们更详细地谈谈这个问题.
当您收到NSPersistentStoreCoordinator Stores Will Change通知时,持久存储仍可供使用,因此与我们去年建议您不得不立即删除持久存储并清除托管对象上下文的情况不同,您仍然可以写入托管对象上下文和这些更改将在本地持久保存,以便在每次更改时都导入到该帐户.
这意味着虽然您的用户的更改不会立即进入iCloud,但如果他们再次登录,他们将会在那里等待.
最后,所有这些商店文件都将由Core Data管理,这意味着我们可以随时删除它们.
一旦其帐户消失,每个商店都将被删除,因为我们可以从云重建文件.
因此,我们希望释放尽可能多的磁盘空间供您的应用程序使用,而不是存在可能占用额外资源的旧存储文件.
还有一点
我们还引入了一个新选项来帮助您创建名为NSPersistentStore 的iCloud持久存储的备份或本地副本删除无处不在的元数据选项.
这将从iCloud存储中删除所有关联的元数据; 这意味着,我们写入元数据字典以及商店文件本身的任何内容,如果您希望使用迁移API在您希望在没有iCloud选项的情况下打开的持久性商店中创建备份或本地副本,这一点至关重要.
另外,请看一下Tim Roadley的书的勘误链接
http://timroadley.com/2014/02/13/learning-core-data-for-ios-errata/
如果您登录到iCloud然后用户更改了应用程序首选项设置(与数据和文档安全设置不同)以关闭您的应用程序iCloud,则应询问用户是否要将现有iCloud存储迁移到本地(再次 - 尝试使用Pages,看看你得到了什么消息).
我发布了一个示例应用程序,在这里完成所有这些.看一下视频,看看预期的行为. http://ossh.com.au/design-and-technology/software-development/
示例应用程序的一些功能包括:
功能包括:
我将尝试回答我自己的问题,部分是为了整理我的想法,部分是为了回复@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];
Run Code Online (Sandbox Code Playgroud)
其实只听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
});
}
Run Code Online (Sandbox Code Playgroud)
然后:
- (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];
}
}
Run Code Online (Sandbox Code Playgroud)
数据迁移是在importContext:intoContext. 此逻辑将取决于您的模型以及重复和冲突策略。
我不知道这是否会产生不需要的副作用。显然,这可能需要相当长的时间,具体取决于持久存储的大小和数据。如果我发现任何问题,我会编辑答案。
| 归档时间: |
|
| 查看次数: |
2355 次 |
| 最近记录: |