UbiquityStoreManager
是一个控制器,为您实现了 Core Data 与 iCloud 集成,并消除了所有困难。
当苹果第一次发布了惊人的 Core Data 和 iCloud 集成时,它被视为极度易整合的 API。不幸的是,正是相反。自那时起,苹果在 iOS 7 中取得了显著的进步。尽管如此,仍有许多需要处理的潜在问题、副作用和未记录的行为,以获得可靠的实现。
UbiquityStoreManager
还提供了一套极具实用性的迁移工具,对于那些希望在用户数据方面有比“默认”的选择更多的控制权的开发者来说。
不幸的是,在 iOS 6 上的用户仍然受到 iCloud 中许多严重错误的困扰,有时会导致云存储不同步,甚至完全损坏。UbiquityStoreManager
会尽可能地处理这些情况。
在保持 API 尽可能简单的同时,为应用开发者提供了所需的钩子以获取所需的确切行为。在可能的地方,UbiquityStoreManager
实现了安全和合理的默认行为来处理异常情况,但如果需要,也可以让您做出不同的选择。这些情况已在 API 文档中详细说明,以及您连接到管理者并实现自定义行为的能力。
我免费提供 UbiquityStoreManager
和其示例应用程序,对此可能对您的应用造成的影响不负任何责任。
创建 UbiquityStoreManager
做出巨大的工作量,至今很少有开发者敢于尝试解决苹果留给我们的 iCloud for Core Data 问题。此代码免费提供,希望它在当前形式或其他形式中有助于您。如果您觉得这个解决方案有用,请考虑表示感谢或捐款。
在iOS 7中,苹果显著提高了Core Data与iCloud的集成。希望从这些改进中受益的应用程序需要更新为仅支持iOS 7。已经更新了支持iOS 7的UbiquityStoreManager
。运行iOS 7+(或OS X 10.9+)的设备将首先将旧数据迁移到新的iOS 7+专用存储,之后将同步到其他iOS 7+/OS X 10.9+设备。iOS 6/OS X 10.8设备将类似于只与自身同步。USM
采用这种方式来隔离旧的具有缺陷的iCloud实现与改进的新实现。
随着iOS 7的改进,为什么你仍然需要使用USM
呢?
USM
为你提供了一个应用程序切换按钮,让用户在全局存储或非全局存储之间切换。USM
附带大量工具,可用于在存储之间迁移。其API简单易用,解决了你应用将面临的问题:如果你的用户想要停止使用iCloud,关闭iCloud开关可以通过一个调用导致USM
切换到本地存储,并且如果用户选择,可以将他的云数据带到本地存储。USM
庞大的代码库即可。如果你使用USM
,你所做的就是-init
一个对象,设置一个代理,等待我们为你提供一个已完全初始化的NSPersistentStoreCoordinator
。让我们来处理这项工作,这样你就可以专注于你的数据模型和应用。要开始使用UbiquityStoreManager
,你所要做的就是实例化它
[[UbiquityStoreManager alloc] initStoreNamed:nil
withManagedObjectModel:nil
localStoreURL:nil
containerIdentifier:nil
additionalStoreOptions:nil
delegate:self]
可以使用nil
参数来自定义UbiquityStoreManager
的行为。例如,如果你已经有一个本地存储,你可以将它的URL作为localStoreURL传递。
然后在你的代理中等待管理器设置你的持久化层
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager willLoadStoreIsCloud:(BOOL)isCloudStore {
self.moc = nil;
}
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didLoadStoreForCoordinator:(NSPersistentStoreCoordinator *)coordinator isCloud:(BOOL)isCloudStore {
self.moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[moc setPersistentStoreCoordinator:coordinator];
[moc setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
}
就是这样!管理器设置了你的NSPersistentStoreCoordinator
,你创建了一个NSManagedObjectContext
,你可以开始了。
请记住,正如上面的代码所显示的,你的nil
。这发生在管理器尚未准备好加载存储时。也可能在存储加载后发生(例如,cloud处于开启或关闭状态,或者存储需要重新加载)。因此,请确保你的应用能够优雅地处理你的主要UbiquityManagedStoreDidChangeNotification
,该操作每次持久化存储的可用性发生变化时都会发布(并且始终在通知你的代理之后)。
最初,管理器将使用本地存储。要启用iCloud(你可以在用户开启iCloud之后做这个),只需切换开关
manager.cloudEnabled = YES;
如果你喜欢,可以使用-setCloudEnabledAndOverwriteCloudWithLocalIfConfirmed:
和-setCloudDisabledAndOverwriteLocalWithCloudIfConfirmed:
方法,这些方法允许你通过确认块向用户询问(在移动到新的(本地或云)存储时)是否希望携带他的当前数据。如果需要,确认块才会触发(新存储已经存在)。如果新存储还不存在,数据将默认迁移。
这取决于你想要多大程度地参与到UbiquityStoreManager
内部如何处理你的存储,以及你想要多大程度地向用户提供关于正在发生的事情的反馈。
例如,当持久性不可用时,你可能希望实现可见反馈(例如,显示一个带有加载轮子的覆盖层)。你可以在ubiquityStoreManager:willLoadStoreIsCloud:
中显示这个轮子,并在ubiquityStoreManager:didLoadStoreForCoordinator:isCloud:
中将其关闭。
还可能是一个好主意,在你的主moc
中更新,每当您从其他设备导入持久性更改时。为此,只需通过从managedObjectContextForUbiquityChangesInManager:
返回它来为管理员提供您的moc
,并可选地注册一个观察者用于UbiquityManagedStoreDidImportChangesNotification
。
不要被误导:事情确实可能出错。对于iOS 6/OS X 10.8用户来说,还有一些问题,这些问题可能会导致云存储成为无法修复的脱节。
UbiquityStoreManager
尽力应对这些问题,主要是自动的。由于管理员非常小心以确保没有数据丢失,有些情况下存储无法自动恢复。因此,实施一些失败处理非常重要,至少按照管理员推荐的方式。
虽然在理论上不应该发生,但有时旨在同步您的云存储与其他设备存储的持久性更改可能与您的云存储不兼容。通常,这种情况是由于苹果iCloud在处理同时来自不同设备编辑的关系时遇到的苹果iCloud错误,这导致无法处理的冲突。有趣的是,错误发生在苹果iCloud实现的最深处,苹果没有通过任何公开的API通知您。UbiquityStoreManager
实现了一种在发生这些问题时检测这些问题并尽可能解决它们的方法。
每当导入事务日志(持久性更改)出现问题,您的应用程序都可以收到通知,并可选择通过在您的代理中实现ubiquityStoreManager:handleCloudContentCorruptionWithHealthyStore:
来介入。如果您只想被告知,然后让管理员处理情况,请返回NO
。如果要以不同于管理员默认方式的方式处理情况,请先处理问题后再返回YES
。
本质上,经理通过卸载与存储冲突的持久性更改的设备的存储,并通知所有其他设备存储已经进入了一个<强>“损坏”强>状态来处理导入异常。其他设备可能不会遇到任何错误(它们可能是损坏日志的作者,或者它们可能没有在其存储和日志之间遇到冲突)。当任何这些<强>“健康”强>设备收到存储损坏的消息时,它们将启动存储重建,创建一个新的云存储,其中包含旧的云存储的实体。此时,所有设备都将切换到新的云存储,损坏状态将清除。
建议您在返回NO
的同时通知用户正在进行什么操作。以下是一个实现示例,它在需要设备等待另一个设备修复损坏时,会向用户提供一个警报。
- (BOOL)ubiquityStoreManager:(UbiquityStoreManager *)manager
handleCloudContentCorruptionWithHealthyStore:(BOOL)storeHealthy {
if (![self.cloudAlert isVisible] && manager.cloudEnabled && !storeHealthy)
dispatch_async( dispatch_get_main_queue(), ^{
self.cloudAlert = [[UIAlertView alloc]
initWithTitle:@"iCloud Sync Problem"
message:@"\n\n\n\n"
@"Waiting for another device to auto‑correct the problem..."
delegate:self
cancelButtonTitle:nil otherButtonTitles:@"Fix Now", nil];
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityIndicator.center = CGPointMake( 142, 90 );
[activityIndicator startAnimating];
[self.cloudAlert addSubview:activityIndicator];
[self.cloudAlert show];
} );
return NO;
}
上述代码给用户提供了点击Fix Now
按钮的选项,这将调用[manager rebuildCloudContentFromCloudStoreOrLocalStore:YES]
。本质上,它在本地启动云存储重建。有关此内容的更多信息稍后介绍。
您的应用程序现在可以处理苹果的iCloud缺陷,恭喜您!
除非您想深入研究,否则就到这里为止。你现在已经完成了。以下内容是给勇敢的人或在寻求最大控制的人准备的。
UbiquityStoreManager
尽最大努力让感兴趣的代理了解正在发生的事情,甚至允许它以非标准方式干预。
如果您使用日志记录器,可以通过实现 ubiquityStoreManager:log:
来连接。每当管理器有事情要说的时候都会调用该方法。我们非常健谈,所以您甚至可能只想为了在生产环境中关闭管理员而实现它。
如果您对获取有关任何错误条件的详细信息感兴趣,请实现 ubiquityStoreManager:didEncounterError:cause:context:
,您将收到通知。
如果云内容被删除,则管理器将卸载持久存储。这可能在用户进入 设置
并删除您的应用的iCloud数据时发生,可能是为了在其iCloud账户上腾出空间。默认情况下,这将使您的应用在没有用户重新启动应用的情况下没有任何持久性。如果应用中仍然启用了iCloud,则会为他创建一个新的存储。您可以根据什么认为是对的来稍微不同地处理它:您可能只想向用户显示一条消息,询问他是否希望禁用或重新启用iCloud。或者您可能只想禁用iCloud并切换到本地存储。您会从 ubiquityStoreManagerHandleCloudContentDeletion:
处理这一点。
如果您仔细阅读了上一部分,应该明白在导入其他设备所做的无处不在的更改期间可能会出现问题。通常,处理这种情况的默认方式可以自动解决问题,但可能需要一些时间才能完全解决,可能还需要用户交互。您可以选择通过实现 ubiquityStoreManager:handleCloudContentCorruptionWithHealthyStore:
并在处理完损坏后返回 YES
来以不同的方式处理这种情况。管理器为您提供以下方法,您可以使用这些方法对存储进行一些低级别的维护:
-reloadStore
— 仅清除并重新打开或重试打开活动存储。-setCloudEnabledAndOverwriteCloudWithLocalIfConfirmed:
— 如果尚未启用,则启用iCloud。如果用户已经有了一个iCloud存储,您的确认块将被调用,允许您询问用户是否想要切换到现有的云数据或用本地数据覆盖它。-setCloudDisableAndOverwriteLocalWithCloudIfConfirmed:
— 如果已启用,则禁用iCloud。如果用户已经有了一个本地存储,您的确认块将被调用,允许您询问用户是否想要切换到现有的本地数据或用云数据覆盖它。-migrateCloudToLocal
— 手动用 iCloud 存储中的数据覆盖用户的本地数据。-migrateLocalToCloud
— 手动用本地存储中的数据覆盖用户的云数据。-deleteCloudContainerLocalOnly:
— 将删除您应用的所有iCloud数据。这不仅是您的Core Data存储!-deleteCloudStoreLocalOnly:
— 将删除您的Core Data云存储。-deleteLocalStore
— 这将删除您的本地存储(即 manager.cloudEnabled
为 NO
时活动的存储)。-rebuildCloudContentFromCloudStoreOrLocalStore:
— 这就是云存储重建魔法的所在地。调用此方法以创建一个新的云存储并将当前云数据复制到其中。许多这些方法都包含一个 localOnly
参数。将其设置为 YES
,如果您不想影响用户的所有iCloud数据。操作将在本地设备上发生。例如,如果您运行 [manager deleteCloudStoreLocalOnly:YES]
,则设备上的云存储将被删除。如果 cloudEnabled
为 YES
,则管理器将随后重新打开云存储,这将导致重新下载商店的所有iCloud事务日志。然后,这些事务日志将在本地重演,您的本地存储将从iCloud中的数据中重新填充。
USM
将始终尽可能地不被破坏。如果迁移或操作失败,它将将用户回滚到变更之前的状态。
parseLogs
parseLogs
bash 脚本允许您分析苹果的详尽 ubiquity 日志输出并提供一些反馈信息。它处于非常初级阶段,但旨在帮助调试与任何 iCloud 相关的同步问题。
要使用脚本,只需运行它(在脚本所在目录下或在将 bashlib
依赖项复制到 PATH
后),将 ubiquity 日志通过 STDIN
传入。
./parseLogs < myapp.logs
要使其更加详尽,请添加 -v
选项。详尽输出将显示未处理的日志行。本脚本的目标是处理苹果的 ubiquity 日志输出的所有日志行。您可以通过修改脚本或 LOGS
摘要文件来做出贡献。
UbiquityStoreManager
努力隐藏所有细节,为您维护持久存储管理器。如果您对它所做的一切以及这些是如何运作的感兴趣,请继续阅读。
UbiquityStoreManager
,本项目。Persistent Store Coordinator
,在存储文件、存储模型和管理上下文之间协调的对象。Managed Object Context
,对数据存储的“视图”。每个上下文都有自己关于存储对象外观的内存中概念。USM 背后的理念是创建一个实现,隐藏所有加载持久存储的细节,允许应用专注于简单地使用它。
为了实现这一点,USM 仅暴露了应用开始使用商店时所需的最小 API,以及一些可能需要用于处理非默认情况的应用的低级 API。USM 实现了合理的默认行为,但允许应用在需要时做出不同的选择。
关于如何设置和配置持久层,有几种不同的方法。USM 已经为您做出了以下选择/假设:
初始化时,USM将开始加载存储。在加载任何存储之前,应用程序的委托首先通过willLoadStoreIsCloud:
被通知。在此阶段,应用程序仍然可以对其进行任何更改,并且由于此方法是从内部持久性队列中调用的,实际上这是执行USM任何初始配置的**推荐**位置。不要从调用USM的init方法的那个方法中这样做。
cloudEnabled
属性确定将加载哪个存储。如果设置为YES
,USM将开始加载云存储。如果设置为NO
,则加载本地存储。
加载本地存储的过程相对简单。如果尚未存在,则会创建存储文件的目录层次结构以及存储文件本身。启用自动轻量级存储模型迁移和映射推断。您可以通过USM的init方法指定额外的存储加载选项,例如文件安全性选项。如果存储成功加载,应用程序将收到通知,并通过didLoadStoreForCoordinator:isCloud:
收到访问存储所需的PSC。如果存储由于某些原因无法加载,应用程序将无法访问持久性,并通过failedLoadingStoreWithCause:context:wasCloud:
获得通知。它可以选择以某种方式处理此故障。
加载云存储的过程略为复杂。主要与本地存储相同,但有一些额外的逻辑。
加载存储时,USM会监视其是否被删除。当云存储被删除时,USM将清理任何“损坏”标记、本地云存储文件,并回退到本地存储,除非应用程序选择通过ubiquityStoreManagerHandleCloudContentDeletion:
处理这种情况。当本地存储被删除时,USM将重新加载,导致创建新的本地存储。
cloudEnabled
设置存储在 NSUserDefaults
的 @"USMCloudEnabledKey"
键下。当 USM 检测到此键的值发生变化时,它将重新加载当前存储,使得更改生效。您可以利用这一点在 Settings.bundle
中为 iCloud 添加偏好设置。
每次应用程序变得活跃时,USM 都会检查 iCloud 身份是否发生变化。如果检测到变化且 iCloud 目前已启用,则存储将被重新加载,以使得更改生效。类似地,当检测到活动无处不在存储的 UUID 发生变化且 iCloud 目前已启用时,存储也会被重新加载。
当检测到云端存储中的无处不在变化时,应用程序的代理可以指定一个自定义 MOC 来用于导入这些更改,以便它可以立即了解这些更改。为此,应用程序应通过 managedObjectContextForUbiquityChangesInManager:
返回其 MOC。如果无处不在更改失败导入,则存储将被重新加载以重试该过程并验证是否发生任何损坏。成功完成后,将发布 UbiquityManagedStoreDidImportChangesNotification
通知。
当云端存储无法加载或云事务日志无法导入时,云存储会被标记为“损坏”。为了检测 Apple 的 Core Data 在事务日志导入尝试中的失败,USM 绕过 NSError
的初始化方法。这样,它可以检测到当创建针对事务日志导入失败的 NSError
时发生的操作,并据此采取相应的行动。
当检测到云端“损坏”时,立即卸载云存储以防止进一步的去同步。当存储当前设备上的存储不健康(存储加载失败或导入无处不在更改失败),USM 将等待,持久化层保持不可用。当存储在当前设备上是健康的,USM 将通过从 iCloud 删除云端内容并创建一个新的云存储(具有新的随机 UUID)来启动云端内容的重建,该 UUID 将从健康的本地云存储文件中播种。新云存储将填充旧云存储的数据,并为其建立健康的云内容。完成后,等待的不健康的设备将注意到一个新云存储变得活跃,加载它,并再次变得健康。应用程序可以通过实现 handleCloudContentCorruptionWithHealthyStore:
来钩入此过程,并修改发生的操作。
任何存储迁移都可以通过以下四种策略之一执行
UbiquityStoreMigrationStrategyIOS
:此策略通过协调 PSC 的 migratePersistentStore:
执行迁移。一些 iOS 版本在这方面存在错误,使其通常不可靠。这是 iOS 7 上的默认策略。UbiquityStoreMigrationStrategyCopyEntities
:此策略通过复制任何非无处不在元数据和复制来自一个存储到另一个存储的所有实体、属性和关系来执行迁移。这是 iOS 6 的默认策略。UbiquityStoreMigrationStrategyManual
:此策略允许应用程序的代理通过实现 manuallyMigrateStore:
来执行迁移。如果您有一个非常大的或复杂的数据模型,或者想要更有控制地迁移实体,这可能很有必要。UbiquityStoreMigrationStrategyNone
:不执行任何迁移,并且以当前的形式打开新存储。UbiquityStoreManager
根据 Apache 许可协议,版本 2.0 许可。
请随意将其用于您的任何应用程序。我也很高兴收到任何评论、反馈或审查任何 pull 请求。