问题描述
在之前的一篇《SwiftData数据同步到iCloud》文章中,写过关于通过modelContainer将SwiftData数据同步到iCloud。
在Xcode中安装应用并测试,发现数据是正常同步的,每次删除应用后,数据都可以从iCloud同步到本地。

但是,在正式环境当中,当我删除“存钱猪猪”应用后,重新安装,发现数据并没有向Xcode测试应用一样,将数据同步到iPhone上。

这一问题可以在App Store下载的审核版本,以及TestFlight的测试版本中得到复现。而通过Xcode安装的应用,无法重现这一问题。

之前也有朋友反馈过,告知他之前好像有创建过存钱罐数据,但现在没有了。

这是第一次遇到这个问题的反馈时,我还调侃内购退款删除存钱罐数据。
后来,我在删除应用后重新测试应用,发现数据没有同步,也就是2月13日我复现了这一问题,实际上这个问题应该早点发现的,主要还是因为不太使用TestFlight进行测试。
TestFlight 安装应用时,应用会默认连接到生产环境(Production)而非开发环境(Development)。这意味着,TestFlight 测试版的应用只能访问生产数据库,不能访问开发数据库。

解决方案
根据网上的解决方案了解到,需要登陆CloudKit控制台,点击左下角的“Deploy Schema Changes…”链接,将架构从开发环境部署到生产环境。

首先,需要登陆到CloudKit控制台。

选择对应的iCloud容器,点击“Deploy Schema Changes…”。

弹出“Confirm Deployment”提示框,表示检查容器架构更改以部署到生产环境。

点击“Deploy”按钮后,完成部署。

调整部署后,应用的iCloud数据将正确的同步到iCloud生产环境容器中。
在TestFlight中测试,发现iCloud数据可以同步到iPhone和Watch,问题得到解决。


为什么测试和生产环境分开部署架构?
这种区分开发(Development)和生产(Production)环境的设计主要是为了确保数据的安全性、稳定性以及测试的可控性。具体来说,以下是为什么要有这种考虑的几个原因:
1、避免影响用户数据
在开发过程中,开发者可能会对数据库架构进行频繁的修改(例如添加新字段、删除旧字段、修改数据模型等)。如果直接在生产环境中进行这些更改,可能会导致以下问题:
数据丢失或损坏:架构更改可能会破坏已有的数据结构,导致数据丢失。
影响用户体验:用户可能会遇到不一致的行为或错误,影响他们使用应用的体验。
因此,开发环境(Development)是一个隔离的测试区域,开发者可以在这里自由地进行架构更改,而不会影响到实际用户的数据。
2、控制架构更改的发布
在开发过程中,架构的修改可能是频繁的并且是临时的。开发者可能会根据测试的反馈不断调整数据结构。而将这些更改直接应用到生产环境可能会导致不可预见的后果,特别是在大规模的用户群体中。使用开发环境和生产环境分开部署架构,可以确保:
只有经过充分测试并确认没有问题的架构更改才会被部署到生产环境。
生产环境的架构保持稳定,不会因为开发阶段的频繁更改而导致问题。
3、数据隔离和安全
开发环境通常是一个较为开放的环境,可以允许开发者进行自由的测试和实验。而生产环境是面向用户的,因此需要严格保证数据的完整性和安全性。通过这种分隔:
开发者可以在开发容器中做任何必要的修改、实验和测试,确保对生产数据没有影响。
生产容器中的数据不会受到开发过程中潜在错误或不稳定修改的影响,确保最终用户的数据安全。
4、数据同步问题
由于开发容器和生产容器是分开的,数据不会自动同步到生产环境。这样做的目的是为了防止开发环境中的临时数据污染生产环境的数据。在准备将开发环境的数据和架构推送到生产环境之前,开发者需要手动执行 “Deploy Schema Changes” 操作,以确保架构在生产环境中能够正常运行。
5、CloudKit 的架构版本控制
CloudKit 是一个云数据库,它支持数据库架构的版本控制。在开发过程中,开发者可以在开发环境中调整架构,并在准备好之后将这些更改部署到 生产环境 中。通过这种方式,开发者可以:
在开发环境中迭代测试架构更改,而不会影响真实用户的数据。
在确保架构稳定并经过验证后,才将更改应用到生产环境,从而确保生产数据的完整性和一致性。
通过在 CloudKit 中分开开发和生产容器,Apple 为开发者提供了一种有效的方式来测试和部署数据库架构,而不影响到真实用户的数据和体验。开发者可以在 开发环境 中自由修改和测试架构,只有在确认架构更改无误时,才将其推送到 生产环境,确保数据安全和应用稳定运行。
总结
这一问题在当时写《SwiftData数据同步到iCloud》文章时,就可以通过TestFlight或正式环境复现的,知道今天才处理,这就导致很多人在使用应用的过程中,因为iCloud的容器架构问题,导致数据无法同步到iCloud,进而存在数据丢失的问题。
吸取的教训是,后续应该优先考虑在TestFlight中测试应用,在生产环境中检查可能存在的问题,再发布到App Store中,避免同样的问题再次发生。
本次问题实际上CloudKit存在开发(Development)和 生产(Production)两个不同的容器,应用在这两个容器中存储数据。在开发过程中更改了数据库架构(比如添加字段、修改表结构等),这些更改首先会反映在开发容器中。
如果希望这些更改也生效于生产容器(即最终用户使用的环境),就需要通过 CloudKit 控制台将架构更改部署到生产容器。
后续补充
在修改架构的四天后(2025年2月28日),重新查看CloudKit,发现错误BAD_REQUEST消失了。
修改架构时间为2025年2月24日,因此24日仍然存在一些报错信息。
2025年2月25日前报错的图表:

2025年2月25日后报错的图表:

经过对比发现,2025年2月25日前的图表,大部分都是BAD_REQUEST报错,占据了50% – 80%的份额。当调整架构的第二天,不在有BAD_REQUEST报错。但还是存在ZONE_NOT_FOUND和QUOTA_EXCEEDED的报错。
报错信息
1、Bad_Request:表示请求无效,通常是由于请求的格式或参数错误。可能传递了错误的字段、数据类型或者缺少必需的参数。需要检查请求的构造,确保所有必要的信息都传递正确。
2、Zone_not_Found:表示指定的 CloudKit 区域(Zone)没有找到。可能是尝试访问的区域不存在,或者区域 ID 有误。如果应用使用了自定义区域(例如私人数据库中的子区域),需确保提供的区域 ID 是正确的。
3、QUOTA_EXceeded:表示 CloudKit 存储配额已达到上限。CloudKit 对每个帐户和应用的存储有配额限制,这个错误通常发生在超过了允许的存储限制时。需要优化数据的存储,或者申请更多的存储空间(如果是免费账户,可能需要升级到付费计划)。
关于Zone_not_Found的知识,目前还没有深入了解。QUOTA_Exceeded报错,可能是用户在使用私有数据库的报错。
参考文章
1、SwiftData数据同步到iCloud:https://fangjunyu.com/2024/12/11/swiftui%e9%80%9a%e8%bf%87%e5%ad%90%e8%a7%86%e5%9b%be%e4%bf%ae%e6%94%b9swiftdata%e5%af%b9%e8%b1%a1/
2、SwiftData not syncing with iCloud:https://developer.apple.com/forums/thread/749352
3、Fix Core Data/SwiftData Cloud Sync Issues in Production Fatbobman’s Blog:https://fatbobman.com/en/snippet/why-core-data-or-swiftdata-cloud-sync-stops-working-after-app-store-login/
4、SwiftData not syncing with iCloud:https://www.reddit.com/r/iOSProgramming/comments/1brffvv/swiftdata_not_syncing_with_icloud/?rdt=33336
5、SOLVED: SwiftUI App failing to sync CloudKit data – but only in TestFlight version:https://www.hackingwithswift.com/forums/swiftui/swiftui-app-failing-to-sync-cloudkit-data-but-only-in-testflight-version/10714