我的应用程序使用CoreData + CloudKit镜像来同步数据,例如在iPhone和手表上。
如果在一个设备上修改了数据,则将修改上载到iCloud,然后与其他设备同步。
正常情况下这很好。然而,很少发生以下情况:
数据在设备上被修改,应用程序被终止。
下一次重新启动应用程序时,不会显示修改过的数据,而是显示未经修改的版本。
我假设(我不知道CoreData + CloudKit镜像在内部是如何工作的)下面的问题。
问题:
考虑以下设置:一个具有一些属性的CoreData实体Item,其中包括updatedAt: Date?。每次更改属性时,都会更新updatedAt,并将Item保存到镜像为iCloud的持久存储中。保存后,更新的Item将导出到iCloud。
当应用程序终止后重新启动时,将导入iCloud版本,这不会产生任何效果,因为它是修改后的版本。然而:
如果应用程序在上传修改版本之前被终止,例如,由于没有网络连接,iCloud仍然拥有未经修改的版本。
重新启动应用程序后,导入具有旧updatedAt值的未修改版本,并用较新的updatedAt值覆盖修改后的版本。所以修改失败了。
可能的解决方案?:
我的第一个想法是使用两个持久存储,一个没有镜像的localStore和一个镜像的mirrorStore。将实体Item分配给这两个存储区。当一个Item被保存时,它会被保存到两个商店。
通常情况下,没有上述问题,两个商店都有相同的Item副本。当获取一个Item时,它只能通过相应地设置fetch请求的affectedStores属性从localStore中获取。
但是,当出现问题时,mirrorStore中的mirrorStore将被旧版本覆盖。这可以通过侦听.NSPersistentStoreRemoteChange对mirrorStore的通知来处理。通知后,您可以从Item和mirrorStore中获取该版本,并使用较新的updatedAt值选择版本。在所描述的场景中,这始终是localStore中的localStore,但是如果稍后在另一个设备上修改了Item,那么mirrorStore中的版本也可能更新。在任何情况下,旧版本都必须用更新的版本覆盖。这可以通过删除旧版本并再次将更新版本保存到两个商店来完成。那么数据也是一致的。
我的问题:
所描述的问题是否存在,或者我是否错过了something?
编辑:
到目前为止,我意识到了这个应用程序意外终止的一个原因。
后台CoreData+CloudKit导出在监视上可能会花费太长时间,请参见以下日志:
2022-03-31 11:18:12.910276+0200 Watch Extension[2388:703470] [BackgroundTask]
Background Task 122 ("CoreData: CloudKit Export"), was created over 30 seconds
ago. In applications running in the background, this creates a risk of termination.
Remember to call UIApplication.endBackgroundTask(_:) for your task in a timely
manner to avoid this.
…
2022-03-31 11:19:00.036156+0200 Watch Extension[2388:703470] [BackgroundTask]
Background task still not ended after expiration handlers were called:
<_UIBackgroundTaskInfo: 0x16514b00>: taskID = 122, taskName = CoreData:
CloudKit Export, creationTime = 61315 (elapsed = 82).
This app will likely be terminated by the system.
Call UIApplication.endBackgroundTask(_:) to avoid this.发布于 2022-05-02 07:19:33
问题:
所描述的问题确实存在。
基本原因是CoreData & CloudKit无法确定CoreData记录( CoreData托管对象)还是镜像的iCloud记录( CKRecord)是“真相的来源”,也就是说,如果它们不同的话,它们是否是有效的记录。
在我可以想象的所有应用程序中,最后修改的记录是有效的记录(错误除外)。
现在,CKRecords有一个系统属性modificationDate,该属性在更改CKRecord时自动更新。但是,CoreData实体没有像modificationDate这样的系统属性。因此,CoreData & CloudKit无法选择后期修改的记录作为真相的来源。
如果应用程序启动或进入前台,CoreData & CloudKit首先从iCloud触发导入并更新持久存储。这意味着尚未导出到iCloud的本地更新(例如,由于网络问题导致导出之前应用程序已被终止)将被覆盖并丢失。
我的解决方案:
我的所有CoreData实体都有一个属性modificationDate。这样的属性也可以自动设置,参见here。我的CoreData记录存储在本地持久存储中,而iCloud私有数据库由CoreData & CloudKit镜像到另一个私有持久存储。当这个私有持久存储通过镜像更新时,它会发送一个.NSPersistentStoreRemoteChange通知。处理通知的函数比较CoreData和iCloud记录的iCloud字段,并选择较新的字段,即更新本地持久存储中的记录或私有持久存储中的记录。
当然,托管上下文和持久存储之间可能存在冲突。这样的冲突也必须通过选择CoreData记录的更新版本来解决。但是,这可以通过使用自定义合并策略来处理,正如所描述的here。
https://stackoverflow.com/questions/71674700
复制相似问题