宝贝计划

照顾宝宝通常是一家人的事。家庭同步让一位用户创建家庭邀请家人加入, 随后家庭成员之间共享「照护对象」(宝宝 / 孕妈 / 宝妈)及其全部记录,包括辅食图片

当前仅 iOS 端支持。Android / Web 暂未接入。

入口

首页右上角「…」菜单 → 家庭管理home.menu.more.family)。 打开 FamilyManagementView,可查看家庭名称、当前角色(创建者 / 成员)、 家庭成员(照护对象)、参与者列表,并发起邀请。

技术架构

同步基于 CloudKit + CKShare,持久层使用 NSPersistentCloudKitContainer(Core Data)

  • 双 storePersistenceController 同时挂载
    • 私有库(.private)— 用户自己的数据;
    • 共享库(.shared)— 通过 CKShare 接收的其他成员家庭数据。
  • 共享根对象CDFamilycontainer.share([family], to:) 生成 CKShare, 由 UICloudSharingControllerCloudSharingControllerView)呈现系统分享面板。
  • 接受邀请SceneDelegate.windowScene(_:userDidAcceptCloudKitShareWith:) 调用 PersistenceController.acceptShare(metadata:),并发出 .familyShareAccepted 通知。
  • 照护对象CDCareSubject(宝宝 / 孕妈 / 宝妈),以 focusKeybaby:<UUID> / pregnancy / newMom)与现有筛选逻辑对应。

加性同步镜像(关键设计)

10 个本地 Store 仍以各自的 JSON 文件作为 UI 数据源(回归风险最低); CloudSyncService 在其之上做一层镜像同步

  • 导出:把本地记录编码为通用信封 SyncEnvelope,写入 CDRecordkind + focusKey + JSON payload + updatedAt + deletedAt 墓碑),挂到 CDFamily
  • 导入:把共享库中的 CDRecord 合并回各 Store,采用 后写入者优先(LWW,按 updatedAt + 墓碑删除。
  • 防回环:导入期间设 isImporting 屏蔽变更回调;导出按 0.6s 去抖。
  • 同步路径在未登录 iCloud 时完全惰性,不影响本地功能。

合并逻辑集中在 SyncCodecenvelopes / merge),并有单元测试覆盖 (新增 / LWW / 墓碑 / 排序)。

图片共享

辅食图片原本只存相册 localIdentifier(仅本机有效)。为跨设备共享, CloudPhotoExporter 读取图片字节写入 CDRecordPhoto(二进制 + 外部存储, 大文件由容器自动转 CKAsset);接收端缓存在 SolidFoodPhotoCache, 作为相册标识符无法解析时的回退数据源。

加入家庭:并入 / 保留私有

接受邀请后(.familyShareAccepted),RootView 弹出二选一:

  • 并入家庭 — 显示家庭成员的共享记录(includeSharedRecords = true);
  • 暂不合并 — 仅显示本地记录。

该选择由 CloudSyncService.includeSharedRecords(UserDefaults 持久化)控制, 可随时在「家庭管理 → 显示家庭共享记录」开关中更改。

独立 Apple 账号 vs 同一 Apple 家庭组

两者都走同一套 CKShare 机制,无需写两套逻辑,区别仅在加入时是否需要手动接受:

  • 独立 Apple 账号:被邀请者需在邀请(链接 / 系统面板)中点击接受。
  • 同一 Apple 家庭组:成员可由系统自动接受(具体行为随 iOS 版本略有差异)。

没有公开 API 可枚举 Apple 家庭组成员名单;分享数据归创建者所有、占用创建者 的 iCloud 空间,被邀请者不额外付费。

配置

  • 工程由 XcodeGen 从 project.yml 生成 —— iCloud 能力、entitlements、 Background Modes 均写在 project.yml,改后须 pnpm ios:gen不要手改 .xcodeproj
  • 容器标识:iCloud.com.gluttongk.apps.illyasviel
  • entitlements:apps/ios/BabyPlan/BabyPlan.entitlements(CloudKit 服务 + aps-environment)。

真机测试清单

CloudKit / CKShare 无法在模拟器或无签名环境验证,需真机实测:

  1. 两台真机分别登录不同 Apple 账号,均开启 iCloud。
  2. 设备 A:家庭管理 → 邀请家人加入 → 通过系统面板发送邀请。
  3. 设备 B:打开邀请 → 接受 → 选择「并入家庭」。
  4. 设备 A 新增一条记录(含辅食图片),确认设备 B 能看到记录与图片。
  5. 设备 B 修改 / 删除记录,确认设备 A 同步更新(LWW / 墓碑)。
  6. 如两台设备属于同一 Apple 家庭组,验证自动接受行为。

本地可验证部分

同步核心逻辑由 BabyPlanTests(host-less 逻辑测试包)覆盖:SyncCodec 合并 / LWW / 墓碑 / 排序,以及 CDRecord / CDRecordPhoto 的 Core Data 往返 (含图片二进制)。运行:

cd apps/ios && xcodegen generate && \
  xcodebuild -project BabyPlan.xcodeproj -scheme BabyPlan \
    -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
    test