架构总览
相关源文件
本页面内容基于以下源文件生成:
IceCream 是一个连接 Realm Database 与 Apple CloudKit 的同步框架,采用离线优先的设计理念,为 iOS/macOS/tvOS/watchOS 全平台提供自动化的数据同步能力。该框架通过封装 CloudKit 的复杂性,让开发者能够以声明式的方式定义需要同步的数据模型,并自动处理网络状态、冲突解决和增量更新等底层细节。
框架的核心设计目标是"像魔法一样工作"——开发者只需将 Realm 对象标记为可同步,框架便会自动监听本地变更、推送到云端,并在收到远程通知时合并数据。这种设计使得应用能够在离线状态下正常工作,待网络恢复后自动完成同步,无需手动管理同步状态或处理复杂的网络边界条件。
整体架构设计
IceCream 采用分层架构设计,将同步逻辑分解为三个主要层次:同步引擎层、数据库管理层和同步对象层。这种分层设计使得各层职责清晰,便于扩展和维护。
正在加载图表渲染器...
架构层次说明
同步引擎层作为框架的顶层入口,负责协调所有同步操作。SyncEngine 类是对外暴露的主要接口,开发者通过初始化该类并传入需要同步的对象列表来启动同步服务。该层不直接处理具体的数据转换,而是将任务委托给数据库管理层执行(IceCream/Classes/SyncEngine.swift:10-16)。
数据库管理层定义了 DatabaseManager 协议,规范了所有数据库操作的标准接口。该层提供了 PrivateDatabaseManager 和 PublicDatabaseManager 两个具体实现,分别对应 CloudKit 的私有数据库和公共数据库。这种策略模式的设计使得框架能够灵活支持不同的数据库作用域,同时保持统一的调用接口(IceCream/Classes/DatabaseManagerProtocol.swift:10-42)。
同步对象层是框架的核心实现层,每个需要同步的 Realm 模型类型都对应一个 SyncObject 实例。该层负责监听 Realm 数据库的变更、将本地对象转换为 CKRecord 格式、处理云端下发的记录变更,以及管理 CKRecordZone 的生命周期(IceCream/Classes/SyncObject.swift:12-24)。
核心设计原则
框架遵循以下核心设计原则:
-
离线优先:所有本地操作优先写入 Realm 数据库,网络同步作为后台任务异步执行,确保应用在无网络状态下仍可正常使用(README.md:13-42)
-
增量同步:通过
CKServerChangeToken机制记录同步进度,每次只获取增量变更,避免全量数据传输带来的性能开销 -
响应式编程:利用 Realm 的通知机制监听数据变更,自动触发同步流程,开发者无需手动调用同步方法
-
职责分离:
SyncEngine负责协调,DatabaseManager负责云端通信,SyncObject负责本地数据转换,各层各司其职
核心组件职责
SyncEngine 同步引擎
SyncEngine 是框架的统一入口点,承担着协调所有同步组件的职责。该类采用外观模式,为开发者提供简洁的 API 接口,同时内部管理着复杂的组件生命周期。
职责边界:
- 直接与 CloudKit 的
CKDatabase交互 - 处理 CloudKit 配置,包括订阅管理
- 将
CKRecordZone相关操作委托给SyncObject处理 - 不直接操作 Realm 数据库
初始化流程:
swift1public convenience init(objects: [Syncable], 2 databaseScope: CKDatabase.Scope = .private, 3 container: CKContainer = .default()) { 4 switch databaseScope { 5 case .private: 6 let privateDatabaseManager = PrivateDatabaseManager(objects: objects, container: container) 7 self.init(databaseManager: privateDatabaseManager) 8 case .public: 9 let publicDatabaseManager = PublicDatabaseManager(objects: objects, container: container) 10 self.init(databaseManager: publicDatabaseManager) 11 default: 12 fatalError("Shared database scope is not supported yet") 13 } 14}
初始化过程根据 databaseScope 参数选择对应的数据库管理器实现。当前版本支持私有数据库和公共数据库,共享数据库尚未实现(IceCream/Classes/SyncEngine.swift:20-31)。
关键 API:
| 方法 | 功能 | 使用场景 |
|---|---|---|
pull(completionHandler:) | 从 CloudKit 拉取数据并合并到本地 | 应用启动时、收到远程推送时 |
pushAll() | 将所有本地数据推送到 CloudKit | 首次同步、数据迁移 |
pull 方法支持回调机制,在私有数据库场景下,当数据获取完成时会调用 completionHandler,错误参数为 nil 表示成功(IceCream/Classes/SyncEngine.swift:68-84)。
SyncObject 同步对象
SyncObject 是框架中最复杂的组件,每个需要同步的 Realm 模型类型都对应一个 SyncObject 泛型实例。该类充当本地数据库与云端同步之间的桥梁。
类型参数约束:
swift1public final class SyncObject<T, U, V, W> where 2 T: Object & CKRecordConvertible & CKRecordRecoverable, 3 U: Object, 4 V: Object, 5 W: Object
类型参数 T 是主同步模型,必须同时遵循 Object(Realm 模型)、CKRecordConvertible(可转换为 CKRecord)和 CKRecordRecoverable(可从 CKRecord 恢复)协议。参数 U、V、W 用于处理关联关系(IceCream/Classes/SyncObject.swift:18)。
核心职责:
-
CKRecordZone 管理:负责创建和管理 CloudKit 记录区域,每个同步模型对应一个独立的 zone
-
Realm 变更检测:通过
NotificationToken监听 Realm 数据库变更,自动捕获本地增删改操作 -
数据格式转换:将 Realm 对象转换为
CKRecord格式用于上传,将CKRecord转换回 Realm 对象用于本地存储
变更监听机制:
swift1private var notificationToken: NotificationToken? 2public var pipeToEngine: ((_ recordsToStore: [CKRecord], 3 _ recordIDsToDelete: [CKRecord.ID]) -> ())?
notificationToken 持有 Realm 通知的订阅令牌,只要该令牌未被释放,变更通知就会持续送达。pipeToEngine 是一个回调闭包,当检测到本地变更时,将需要存储的记录和需要删除的记录 ID 传递给上层引擎处理(IceCream/Classes/SyncObject.swift:22-24)。
Syncable 协议
Syncable 协议定义了同步对象必须实现的标准接口,是框架扩展性的基础。任何类型只要实现了该协议,就可以被 SyncEngine 管理。
协议定义:
swift1public protocol Syncable: class { 2 // CKRecordZone 相关 3 var recordType: String { get } 4 var zoneID: CKRecordZone.ID { get } 5 6 // 本地存储状态 7 var zoneChangesToken: CKServerChangeToken? { get set } 8 var isCustomZoneCreated: Bool { get set } 9 10 // Realm 数据库操作 11 func registerLocalDatabase() 12 func cleanUp() 13 func add(record: CKRecord) 14 func delete(recordID: CKRecord.ID) 15 func resolvePendingRelationships() 16 17 // CloudKit 操作 18 func pushLocalObjectsToCloudKit() 19 20 // 回调 21 var pipeToEngine: ((_ recordsToStore: [CKRecord], 22 _ recordIDsToDelete: [CKRecord.ID]) -> ())? { get set } 23}
接口分组说明:
| 分组 | 属性/方法 | 用途 |
|---|---|---|
| CKRecordZone | recordType, zoneID | 标识 CloudKit 记录类型和区域 |
| 本地状态 | zoneChangesToken, isCustomZoneCreated | 追踪同步进度和区域创建状态 |
| 数据库操作 | add, delete, cleanUp | 增删改本地数据 |
| 关系处理 | resolvePendingRelationships | 延迟解析对象间关系 |
| 云端推送 | pushLocalObjectsToCloudKit | 主动推送本地数据 |
zoneChangesToken 是增量同步的关键,它记录了上次同步的位置,下次同步时只需获取该 token 之后的变更(IceCream/Classes/Syncable.swift:12-38)。
DatabaseManager 协议
DatabaseManager 协议定义了数据库管理器的标准接口,是策略模式的核心抽象。通过该协议,框架能够以统一的方式处理不同作用域的 CloudKit 数据库。
核心方法:
| 方法 | 功能 | 调用时机 |
|---|---|---|
prepare() | 准备同步环境 | 初始化完成后 |
fetchChangesInDatabase(_:) | 获取数据库变更 | pull 操作触发 |
resumeLongLivedOperationIfPossible() | 恢复长生命周期操作 | 应用启动时 |
createCustomZonesIfAllowed() | 创建自定义区域 | 首次同步前 |
startObservingRemoteChanges() | 开始监听远程变更 | 初始化时 |
registerLocalDatabase() | 注册本地数据库监听 | 初始化时 |
长生命周期操作恢复:
该机制解决了应用在同步过程中被终止的问题。当应用重新启动时,resumeLongLivedOperationIfPossible 方法会检查是否有未完成的 CloudKit 操作,并自动恢复执行。这确保了即使在同步过程中应用被用户强制退出或系统终止,数据同步仍能最终完成(IceCream/Classes/DatabaseManagerProtocol.swift:26-33)。
数据库管理策略
私有数据库管理器
PrivateDatabaseManager 负责管理 CloudKit 私有数据库的同步操作。私有数据库数据存储在用户的 iCloud 空间中,只有用户本人可以访问。
初始化实现:
swift1final class PrivateDatabaseManager: DatabaseManager { 2 let container: CKContainer 3 let database: CKDatabase 4 let syncObjects: [Syncable] 5 6 public init(objects: [Syncable], container: CKContainer) { 7 self.syncObjects = objects 8 self.container = container 9 self.database = container.privateCloudDatabase 10 } 11}
初始化时直接绑定到 container.privateCloudDatabase,所有后续操作都针对该私有数据库实例(IceCream/Classes/PrivateDatabaseManager.swift:16-27)。
私有数据库特性:
- 数据消耗用户的 iCloud 存储配额
- 支持订阅通知,当云端数据变更时会推送静默通知
- 支持长生命周期操作,可在应用重启后恢复
- 适合存储用户个人数据,如笔记、待办事项等
公共数据库管理器
PublicDatabaseManager 负责管理 CloudKit 公共数据库的同步操作。公共数据库数据存储在开发者的 iCloud 容器中,所有用户都可以读取。
初始化实现:
swift1final class PublicDatabaseManager: DatabaseManager { 2 let container: CKContainer 3 let database: CKDatabase 4 let syncObjects: [Syncable] 5 6 init(objects: [Syncable], container: CKContainer) { 7 self.syncObjects = objects 8 self.container = container 9 self.database = container.publicCloudDatabase 10 } 11}
与私有数据库管理器类似,但绑定到 container.publicCloudDatabase(IceCream/Classes/PublicDatabaseManager.swift:16-27)。
公共数据库特性:
- 数据存储在开发者的容器配额中,不消耗用户 iCloud 空间
- 所有用户可读取,但写入权限需要通过权限控制
- 适合存储共享数据,如应用配置、公共资源等
数据库选择策略
框架通过 SyncEngine 的初始化参数 databaseScope 让开发者选择数据库类型:
swift1// 使用私有数据库(默认) 2let syncEngine = SyncEngine(objects: [syncObject1, syncObject2]) 3 4// 使用公共数据库 5let syncEngine = SyncEngine(objects: [syncObject1, syncObject2], 6 databaseScope: .public)
当前版本不支持共享数据库,尝试使用 .shared 作用域会触发 fatalError(IceCream/Classes/SyncEngine.swift:28-30)。
同步流程机制
数据拉取流程
当应用启动或收到远程推送通知时,SyncEngine 会触发 pull 操作,从 CloudKit 获取最新的数据变更并合并到本地 Realm 数据库。
正在加载图表渲染器...
流程关键点:
-
增量获取:通过
CKServerChangeToken只获取上次同步之后的变更,避免全量传输 -
分记录类型处理:
DatabaseManager遍历所有SyncObject,根据recordType将变更分发给对应的同步对象处理 -
延迟关系解析:由于 CloudKit 的关系字段可能引用尚未同步的对象,框架先写入所有记录,再统一解析关系
-
错误传递:任何步骤发生错误都会通过回调传递给调用方,由应用层决定重试策略
数据推送流程
当本地 Realm 数据库发生变更时,SyncObject 通过通知机制捕获变更,并通过 pipeToEngine 回调将变更传递给上层,最终推送到 CloudKit。
正在加载图表渲染器...
推送策略:
- 批量操作:将多个变更打包为单个
CKModifyRecordsOperation,减少网络请求次数 - 自动重试:CloudKit 操作支持自动重试,网络恢复后会继续尝试
- 冲突处理:使用
savePolicy策略处理云端与本地冲突
手动同步触发
除了自动同步,框架还提供了手动触发同步的接口:
swift1// 手动拉取云端数据 2syncEngine.pull { error in 3 if let error = error { 4 print("同步失败: \(error)") 5 } else { 6 print("同步成功") 7 } 8} 9 10// 手动推送所有本地数据(谨慎使用) 11syncEngine.pushAll()
pushAll 方法会遍历所有 SyncObject,调用其 pushLocalObjectsToCloudKit 方法,将本地所有数据推送到云端。该方法适用于首次同步或数据迁移场景,不建议频繁调用(IceCream/Classes/SyncEngine.swift:78-82)。
核心设计决策与取舍
架构模式选择
外观模式:SyncEngine 作为外观类,隐藏了 DatabaseManager 和 SyncObject 的复杂性,为开发者提供简洁的初始化和同步接口。这种设计降低了框架的使用门槛,同时保持了内部实现的灵活性。
策略模式:通过 DatabaseManager 协议定义统一接口,PrivateDatabaseManager 和 PublicDatabaseManager 作为具体策略实现。这种设计使得添加新的数据库类型(如共享数据库)时,只需新增策略类,无需修改现有代码。
观察者模式:利用 Realm 的 NotificationToken 机制监听数据变更,当本地数据发生变化时自动触发同步流程。这种响应式设计避免了手动调用同步方法的繁琐。
技术选型理由
| 技术选型 | 选型理由 | 已知限制 |
|---|---|---|
| Realm Database | 原生支持移动端、线程安全、响应式通知、零拷贝性能 | 增加应用包体积、跨平台迁移成本 |
| CloudKit | Apple 原生云服务、免费配额充足、静默推送支持 | 仅限 Apple 生态、API 相对复杂 |
| CKRecordZone | 支持原子批量操作、独立同步进度追踪 | 每个 zone 有操作数量限制 |
| CKServerChangeToken | 增量同步、减少数据传输、支持断点续传 | Token 与 zone 绑定,zone 删除后 token 失效 |
| 泛型 SyncObject | 类型安全、编译期检查、避免运行时错误 | 类型参数较多,语法复杂 |
已知限制与边界条件
-
共享数据库不支持:当前版本仅支持私有和公共数据库,尝试使用
.shared作用域会触发致命错误(IceCream/Classes/SyncEngine.swift:28-30) -
关系解析延迟:由于 CloudKit 关系字段可能引用尚未同步的对象,框架采用两阶段提交策略,先写入所有记录,再统一解析关系
-
文档不完整:README 中标记文档状态为未完成,开发者需要阅读源码了解高级用法(README.md:42)
-
推送频率限制:
pushAll方法注释明确提示不应频繁调用,频繁全量推送可能导致 CloudKit 限流
技术选型对比
| 技术组件 | 用途 | 选型理由 | 替代方案 |
|---|---|---|---|
| Realm Database | 本地数据存储 | 离线优先、线程安全、响应式通知、移动端优化 | Core Data、SQLite |
| CloudKit | 云端同步服务 | Apple 原生、自动认证、静默推送、免费配额 | Firebase、AWS Amplify |
| CKDatabase.Scope | 数据库作用域 | 区分私有/公共数据访问权限 | 自定义权限系统 |
| CKRecordZone | 同步分区 | 原子操作、独立同步进度、批量操作支持 | 单记录操作 |
| CKServerChangeToken | 增量同步标记 | 减少数据传输、支持断点续传 | 全量同步 |
| NotificationToken | Realm 变更监听 | 自动捕获本地变更、响应式编程 | 手动触发同步 |
| CKModifyRecordsOperation | 批量记录修改 | 减少网络请求、支持原子提交 | 单记录操作 |
| Long-lived Operation | 长生命周期操作 | 应用重启后恢复未完成任务 | 放弃未完成任务 |
模块依赖关系
正在加载图表渲染器...
依赖方向说明:
- SyncEngine → DatabaseManager:引擎依赖协议而非具体实现,符合依赖倒置原则
- DatabaseManager → Syncable:管理器通过协议操作同步对象,保持松耦合
- SyncObject → Realm/CKRecord:同步对象同时依赖本地数据库和云端记录格式,承担数据转换职责
- Private/PublicManager → CloudKit:具体管理器实现依赖 CloudKit SDK 进行网络通信
关键配置与启动流程
初始化配置
框架的初始化需要提供以下配置:
swift1// 1. 定义 Realm 模型(需遵循 CKRecordConvertible 和 CKRecordRecoverable) 2class Todo: Object, CKRecordConvertible, CKRecordRecoverable { 3 @objc dynamic var id: String = UUID().uuidString 4 @objc dynamic var text: String = "" 5 @objc dynamic var isCompleted: Bool = false 6} 7 8// 2. 创建 SyncObject 实例 9let todoSyncObject = SyncObject<Todo, Object, Object, Object>( 10 // 配置参数 11) 12 13// 3. 初始化 SyncEngine 14let syncEngine = SyncEngine( 15 objects: [todoSyncObject], 16 databaseScope: .private, 17 container: .default() 18)
启动流程
-
SyncEngine 初始化:根据
databaseScope创建对应的DatabaseManager实现(IceCream/Classes/SyncEngine.swift:20-31) -
调用 setup():内部调用
databaseManager.prepare()准备同步环境 -
创建自定义区域:调用
createCustomZonesIfAllowed()在 CloudKit 中创建记录区域 -
注册数据库订阅:调用
createDatabaseSubscriptionIfHaveNot()创建推送订阅 -
开始监听变更:
- 调用
startObservingRemoteChanges()监听云端推送 - 调用
registerLocalDatabase()监听本地 Realm 变更
- 调用
-
恢复长生命周期操作:调用
resumeLongLivedOperationIfPossible()恢复未完成的同步任务
应用生命周期集成
框架需要在 AppDelegate 或 SceneDelegate 中正确集成:
swift1// AppDelegate.swift 2func application(_ application: UIApplication, 3 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 4 // 初始化 SyncEngine 5 syncEngine = SyncEngine(objects: [...]) 6 return true 7} 8 9func application(_ application: UIApplication, 10 didReceiveRemoteNotification userInfo: [AnyHashable: Any], 11 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { 12 // 处理 CloudKit 静默推送 13 syncEngine.pull { error in 14 completionHandler(error == nil ? .newData : .failed) 15 } 16}
这种集成方式确保了应用在启动时能够恢复同步状态,在收到推送时能够及时拉取云端变更。
