Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core Data with CloudKit(二) —— 同步本地数据库到 iCloud 私有数据库 | 肘子的Swift记事本 #113

Open
fatbobman opened this issue Oct 13, 2021 · 25 comments

Comments

@fatbobman
Copy link
Owner

https://www.fatbobman.com/posts/coreDataWithCloudKit-2/

本篇文章中,我们将探讨 Core Data with CloudKit 应用中最常见的场景——将本地数据库同步到 iCloud 私有数据库。

@akring
Copy link

akring commented Jan 19, 2022

读完这篇 CloudKit 相关的博文,受益匪浅,有两个小问题想请教:

  1. CloudKit 不支持 Unique constraints,那么在实践中,保证数据唯一性的最佳实践应该是什么呢?

  2. 经过测试,勾选了 ordered 的 Relationship 也同样不受 CloudKit 支持,这个有没有其他的替代方案呢?

测试用的项目为 SwiftUI + CoreData,后期尝试加入 CloudKit 同步支持

@fatbobman
Copy link
Owner Author

fatbobman commented Jan 19, 2022 via email

@akring
Copy link

akring commented Jan 19, 2022

好的,感谢答疑🙏

@tailang
Copy link

tailang commented Jul 14, 2022

肘子哥您好,请教个问题,我这有这样一个需求:如果用户不是会员的时候,不同步Core Data到Cloud,如果后来该用户开通了会员,需要将Core Data同步到Cloud,我是否可以通过设置cloudDesc.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "your.cloudKit.container")来控制

@fatbobman
Copy link
Owner Author

肘子哥您好,请教个问题,我这有这样一个需求:如果用户不是会员的时候,不同步Core Data到Cloud,如果后来该用户开通了会员,需要将Core Data同步到Cloud,我是否可以通过设置cloudDesc.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "your.cloudKit.container")来控制

你好,可以的。cloudDesc.cloudKitContainerOptions 为 nil 时 NSPersistentCloudKitContainer 其实就是个 NSPersistentContainer 。在启动时检查用于是否为会员,从而实现不同的设置可以实现你的需求。不过,如果你只使用一个 container 的话,app 需要在冷启动的情况下,配置才会起作用。也就是说,用户开了会员,只有在下次 app 彻底重启后,同步功能才会被启用。
实时切换也是可以的,但会麻烦不少。首先要在 stack 中读取 momd 文件创建 model,在 stack 中定义两个 Container ( 一个本地、一个云 ),都使用 lazy 加载的方式。创建一个动态变量 container ,根据是否开会员返回对应的 container 实例。

@tailang
Copy link

tailang commented Jul 14, 2022

@fatbobman

肘子哥您好,请教个问题,我这有这样一个需求:如果用户不是会员的时候,不同步Core Data到Cloud,如果后来该用户开通了会员,需要将Core Data同步到Cloud,我是否可以通过设置cloudDesc.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "your.cloudKit.container")来控制

你好,可以的。cloudDesc.cloudKitContainerOptions 为 nil 时 NSPersistentCloudKitContainer 其实就是个 NSPersistentContainer 。在启动时检查用于是否为会员,从而实现不同的设置可以实现你的需求。不过,如果你只使用一个 container 的话,app 需要在冷启动的情况下,配置才会起作用。也就是说,用户开了会员,只有在下次 app 彻底重启后,同步功能才会被启用。
实时切换也是可以的,但会麻烦不少。首先要在 stack 中读取 momd 文件创建 model,在 stack 中定义两个 Container ( 一个本地、一个云 ),都使用 lazy 加载的方式。创建一个动态变量 container ,根据是否开会员返回对应的 container 实例。

感谢肘子哥答疑,阅读了这个系列的文章受益匪浅。

@gaowei-china
Copy link

老哥,真机测试时添加数据再删除app重新跑可以还原iCloud的数据,发布到App Store后安装新增数据然后删除app重新安装却不能还原iCloud数据呢?请问是哪里少设置了吗?

@fatbobman
Copy link
Owner Author

老哥,真机测试时添加数据再删除app重新跑可以还原iCloud的数据,发布到App Store后安装新增数据然后删除app重新安装却不能还原iCloud数据呢?请问是哪里少设置了吗?

开发环境中的数据与发行环境中的数据是无法互通的。
另外,你还要检查一下,是否在 cloudKit 控制台中,对设定更改进行了发布。否则在发布环境下( App Store、testFlight )是无法实现网络同步的。

image

@kingslay
Copy link

你好。想要根据Attribute来判断这个数据是否要同步到iCloud,我看你有提供了一个方案是用两个container: 但是对于你最后一步的处理看的不是很懂,是否可以更详细的指导下,或是有文章链接吗?在网站找了很久,没有找到把viewContext 的数据同步到viewContext的方式。难道是要把数据查询出来,然后复制一个model,在插入到数据库里吗? 谢谢
处理NSPersistentStoreRemoteChange通知,将从localContainer中写入的数据合并到container的viewContext中。

@fatbobman
Copy link
Owner Author

你好。想要根据Attribute来判断这个数据是否要同步到iCloud,我看你有提供了一个方案是用两个container: 但是对于你最后一步的处理看的不是很懂,是否可以更详细的指导下,或是有文章链接吗?在网站找了很久,没有找到把viewContext 的数据同步到viewContext的方式。难道是要把数据查询出来,然后复制一个model,在插入到数据库里吗? 谢谢 处理NSPersistentStoreRemoteChange通知,将从localContainer中写入的数据合并到container的viewContext中。

如果想对同一个 Entity 的数据进行有选择性的同步,也可以只使用一个 container。
在模型编辑器中创建两个 Configuration ,其中分别包含了该 Entity
在 Stack 中,loadDescription 之前,只给其中一个设置 CloudKitOption,并分别设置对应的 URL
之后,在代码中 fetch 该 Entity 数据时,Core Data 会自动将两个不同的数据库中的 Entity 数据合并,并统一返回给你
在保存数据时,你可以使用托管上下文的 assign 方法,为其设置对应的持久化存储,这样就可以在代码中决定哪个数据要同步,哪个不同步
https://developer.apple.com/documentation/coredata/nsmanagedobjectcontext/1506436-assign
因为要依靠 NSPersistentStore 来决定是否同步,因此,在 Stack 中,loadDescription 后,最好保存一下同步和非同步的 Store,方便之后的保存方法使用。

@kingslay
Copy link

非常感谢你的回复,我有试过这个方法,但是因为我的数据是一开始保存到本地,只有当满足一定的条件了,才想要推送到iCloud。所以我用这个方法目前报错Can't reassign an object to a different store once it has been saved.
看了网上的解决方案是要先把数据复制一份,然后插入到有配置同步的数据库,然后从原有的数据库里把这个数据给删了。但是这样太麻烦了。

@fatbobman
Copy link
Owner Author

需要删除原来的数据,在另一个 store 中创建新的。其实封装成一个方法,并不麻烦。

@I-NOCoder
Copy link

你好,请问,笔记中的图片数据也要使用 core data 进行同步吗?开启 Allows External Storage 有没有影响?谢谢。

@fatbobman
Copy link
Owner Author

@I-NOCoder 如果数据库需要同步,且图片数据保存在数据库内,那么在同步的过程中,图片数据也会一并进行同步的。开启 allow external storage 不受影响,可以正常同步

@I-NOCoder
Copy link

@fatbobman
@I-NOCoder 如果数据库需要同步,且图片数据保存在数据库内,那么在同步的过程中,图片数据也会一并进行同步的。开启 allow external storage 不受影响,可以正常同步

谢谢,如果图片数据多的话,对 core data 影响大不大?

@fatbobman
Copy link
Owner Author

@I-NOCoder 启用外部存储的话,不会影响 Core Data 的效率。但是数量量大的话,必然会影响同步的时间。

@I-NOCoder
Copy link

@fatbobman
@I-NOCoder 启用外部存储的话,不会影响 Core Data 的效率。但是数量量大的话,必然会影响同步的时间。

好的,感谢

@spencerfeng
Copy link

你好,如果我把录音文件保存在文件系统,在core data里保存文件的路径,怎样做才能保证同时录音文件和保存在core data的路径都能使用iCloud同步呢?

@fatbobman
Copy link
Owner Author

@spencerfeng 如果文件容量较大,可以考虑与 iCloud Documents 结合使用。将文件保存在共享目录中。在另一台设备上,获取到 url 后,需要首先检查当前的状态,如果为占位符,选择下载。如果容量较小,还是推荐保存在 Core Data 中,可以开启保存在外部的选项。这样就无需自己处理同步了。
https://fatbobman.com/zh/posts/in-depth-guide-to-icloud-documents/

@spencerfeng
Copy link

非常感谢🙏

@wdlgame
Copy link

wdlgame commented Apr 8, 2024

“官方文档中这个限制我比较困惑,因为即使不采用网络同步,开发者也通常不会为两个 Configuration 中的实体建立 relationship。如果需要建立联系,通常会采用创建 Fetched Properties”

这里有问题请教,当一些数据为公共数据库,另外一些通过Private数据库进行同步时,此时就无法自然的建立 relationship了。而采用创建 Fetched Properties 是要如何做呢?本来有关系的两个数据,现在因为一个在公共数据库,一个在私有数据库而无法建立relationship。

@fatbobman
Copy link
Owner Author

Fetched Properties 其实就相当于预设了谓词的形式进行获取。
建议你在这种情况下,为两边的数据分别设置可以用作检索的谓词属性。数据编辑器中的 Fetched Properties 灵活性比较差
分别在不同的数据库中进行检索。
比如,一边有一个属性 uid , 那么就在另一边也保存对应的 uid ,通过这个 uid 进行检索。

@ybwdaisy
Copy link

在keyboard extension中通过viewContext.fetch拿到的不是main app中最新的数据,如何解决?

@fatbobman
Copy link
Owner Author

@ybwdaisy 的 keyboard extension 没有了解。不过照理来说,应该在 extension 或 widget 中,使用持久化历史跟踪来保证数据的一致性。
https://fatbobman.com/zh/posts/persistent-history-tracking-in-swiftdata/

@ybwdaisy
Copy link

@fatbobman
@ybwdaisy 的 keyboard extension 没有了解。不过照理来说,应该在 extension 或 widget 中,使用持久化历史跟踪来保证数据的一致性。
https://fatbobman.com/zh/posts/persistent-history-tracking-in-swiftdata/

感谢,我先试试 https://github.com/fatbobman/PersistentHistoryTrackingKit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants