价格

架构总览

相关源文件

本页面内容基于以下源文件生成:

IceCream 是一个连接 Realm Database 与 Apple CloudKit 的同步框架,采用离线优先的设计理念,为 iOS/macOS/tvOS/watchOS 全平台提供自动化的数据同步能力。该框架通过封装 CloudKit 的复杂性,让开发者能够以声明式的方式定义需要同步的数据模型,并自动处理网络状态、冲突解决和增量更新等底层细节。

框架的核心设计目标是"像魔法一样工作"——开发者只需将 Realm 对象标记为可同步,框架便会自动监听本地变更、推送到云端,并在收到远程通知时合并数据。这种设计使得应用能够在离线状态下正常工作,待网络恢复后自动完成同步,无需手动管理同步状态或处理复杂的网络边界条件。

整体架构设计

IceCream 采用分层架构设计,将同步逻辑分解为三个主要层次:同步引擎层、数据库管理层和同步对象层。这种分层设计使得各层职责清晰,便于扩展和维护。

正在加载图表渲染器...

架构层次说明

同步引擎层作为框架的顶层入口,负责协调所有同步操作。SyncEngine 类是对外暴露的主要接口,开发者通过初始化该类并传入需要同步的对象列表来启动同步服务。该层不直接处理具体的数据转换,而是将任务委托给数据库管理层执行(IceCream/Classes/SyncEngine.swift:10-16)。

数据库管理层定义了 DatabaseManager 协议,规范了所有数据库操作的标准接口。该层提供了 PrivateDatabaseManagerPublicDatabaseManager 两个具体实现,分别对应 CloudKit 的私有数据库和公共数据库。这种策略模式的设计使得框架能够灵活支持不同的数据库作用域,同时保持统一的调用接口(IceCream/Classes/DatabaseManagerProtocol.swift:10-42)。

同步对象层是框架的核心实现层,每个需要同步的 Realm 模型类型都对应一个 SyncObject 实例。该层负责监听 Realm 数据库的变更、将本地对象转换为 CKRecord 格式、处理云端下发的记录变更,以及管理 CKRecordZone 的生命周期(IceCream/Classes/SyncObject.swift:12-24)。

核心设计原则

框架遵循以下核心设计原则:

  1. 离线优先:所有本地操作优先写入 Realm 数据库,网络同步作为后台任务异步执行,确保应用在无网络状态下仍可正常使用(README.md:13-42

  2. 增量同步:通过 CKServerChangeToken 机制记录同步进度,每次只获取增量变更,避免全量数据传输带来的性能开销

  3. 响应式编程:利用 Realm 的通知机制监听数据变更,自动触发同步流程,开发者无需手动调用同步方法

  4. 职责分离SyncEngine 负责协调,DatabaseManager 负责云端通信,SyncObject 负责本地数据转换,各层各司其职

核心组件职责

SyncEngine 同步引擎

SyncEngine 是框架的统一入口点,承担着协调所有同步组件的职责。该类采用外观模式,为开发者提供简洁的 API 接口,同时内部管理着复杂的组件生命周期。

职责边界

  • 直接与 CloudKit 的 CKDatabase 交互
  • 处理 CloudKit 配置,包括订阅管理
  • CKRecordZone 相关操作委托给 SyncObject 处理
  • 不直接操作 Realm 数据库

初始化流程

swift
1public 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 泛型实例。该类充当本地数据库与云端同步之间的桥梁。

类型参数约束

swift
1public 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 恢复)协议。参数 UVW 用于处理关联关系(IceCream/Classes/SyncObject.swift:18)。

核心职责

  1. CKRecordZone 管理:负责创建和管理 CloudKit 记录区域,每个同步模型对应一个独立的 zone

  2. Realm 变更检测:通过 NotificationToken 监听 Realm 数据库变更,自动捕获本地增删改操作

  3. 数据格式转换:将 Realm 对象转换为 CKRecord 格式用于上传,将 CKRecord 转换回 Realm 对象用于本地存储

变更监听机制

swift
1private 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 管理。

协议定义

swift
1public 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}

接口分组说明

分组属性/方法用途
CKRecordZonerecordType, 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 空间中,只有用户本人可以访问。

初始化实现

swift
1final 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 容器中,所有用户都可以读取。

初始化实现

swift
1final 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.publicCloudDatabaseIceCream/Classes/PublicDatabaseManager.swift:16-27)。

公共数据库特性

  • 数据存储在开发者的容器配额中,不消耗用户 iCloud 空间
  • 所有用户可读取,但写入权限需要通过权限控制
  • 适合存储共享数据,如应用配置、公共资源等

数据库选择策略

框架通过 SyncEngine 的初始化参数 databaseScope 让开发者选择数据库类型:

swift
1// 使用私有数据库(默认)
2let syncEngine = SyncEngine(objects: [syncObject1, syncObject2])
3
4// 使用公共数据库
5let syncEngine = SyncEngine(objects: [syncObject1, syncObject2], 
6                           databaseScope: .public)

当前版本不支持共享数据库,尝试使用 .shared 作用域会触发 fatalErrorIceCream/Classes/SyncEngine.swift:28-30)。

同步流程机制

数据拉取流程

当应用启动或收到远程推送通知时,SyncEngine 会触发 pull 操作,从 CloudKit 获取最新的数据变更并合并到本地 Realm 数据库。

正在加载图表渲染器...

流程关键点

  1. 增量获取:通过 CKServerChangeToken 只获取上次同步之后的变更,避免全量传输

  2. 分记录类型处理DatabaseManager 遍历所有 SyncObject,根据 recordType 将变更分发给对应的同步对象处理

  3. 延迟关系解析:由于 CloudKit 的关系字段可能引用尚未同步的对象,框架先写入所有记录,再统一解析关系

  4. 错误传递:任何步骤发生错误都会通过回调传递给调用方,由应用层决定重试策略

数据推送流程

当本地 Realm 数据库发生变更时,SyncObject 通过通知机制捕获变更,并通过 pipeToEngine 回调将变更传递给上层,最终推送到 CloudKit。

正在加载图表渲染器...

推送策略

  • 批量操作:将多个变更打包为单个 CKModifyRecordsOperation,减少网络请求次数
  • 自动重试:CloudKit 操作支持自动重试,网络恢复后会继续尝试
  • 冲突处理:使用 savePolicy 策略处理云端与本地冲突

手动同步触发

除了自动同步,框架还提供了手动触发同步的接口:

swift
1// 手动拉取云端数据
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 作为外观类,隐藏了 DatabaseManagerSyncObject 的复杂性,为开发者提供简洁的初始化和同步接口。这种设计降低了框架的使用门槛,同时保持了内部实现的灵活性。

策略模式:通过 DatabaseManager 协议定义统一接口,PrivateDatabaseManagerPublicDatabaseManager 作为具体策略实现。这种设计使得添加新的数据库类型(如共享数据库)时,只需新增策略类,无需修改现有代码。

观察者模式:利用 Realm 的 NotificationToken 机制监听数据变更,当本地数据发生变化时自动触发同步流程。这种响应式设计避免了手动调用同步方法的繁琐。

技术选型理由

技术选型选型理由已知限制
Realm Database原生支持移动端、线程安全、响应式通知、零拷贝性能增加应用包体积、跨平台迁移成本
CloudKitApple 原生云服务、免费配额充足、静默推送支持仅限 Apple 生态、API 相对复杂
CKRecordZone支持原子批量操作、独立同步进度追踪每个 zone 有操作数量限制
CKServerChangeToken增量同步、减少数据传输、支持断点续传Token 与 zone 绑定,zone 删除后 token 失效
泛型 SyncObject类型安全、编译期检查、避免运行时错误类型参数较多,语法复杂

已知限制与边界条件

  1. 共享数据库不支持:当前版本仅支持私有和公共数据库,尝试使用 .shared 作用域会触发致命错误(IceCream/Classes/SyncEngine.swift:28-30

  2. 关系解析延迟:由于 CloudKit 关系字段可能引用尚未同步的对象,框架采用两阶段提交策略,先写入所有记录,再统一解析关系

  3. 文档不完整:README 中标记文档状态为未完成,开发者需要阅读源码了解高级用法(README.md:42

  4. 推送频率限制pushAll 方法注释明确提示不应频繁调用,频繁全量推送可能导致 CloudKit 限流

技术选型对比

技术组件用途选型理由替代方案
Realm Database本地数据存储离线优先、线程安全、响应式通知、移动端优化Core Data、SQLite
CloudKit云端同步服务Apple 原生、自动认证、静默推送、免费配额Firebase、AWS Amplify
CKDatabase.Scope数据库作用域区分私有/公共数据访问权限自定义权限系统
CKRecordZone同步分区原子操作、独立同步进度、批量操作支持单记录操作
CKServerChangeToken增量同步标记减少数据传输、支持断点续传全量同步
NotificationTokenRealm 变更监听自动捕获本地变更、响应式编程手动触发同步
CKModifyRecordsOperation批量记录修改减少网络请求、支持原子提交单记录操作
Long-lived Operation长生命周期操作应用重启后恢复未完成任务放弃未完成任务

模块依赖关系

正在加载图表渲染器...

依赖方向说明

  • SyncEngine → DatabaseManager:引擎依赖协议而非具体实现,符合依赖倒置原则
  • DatabaseManager → Syncable:管理器通过协议操作同步对象,保持松耦合
  • SyncObject → Realm/CKRecord:同步对象同时依赖本地数据库和云端记录格式,承担数据转换职责
  • Private/PublicManager → CloudKit:具体管理器实现依赖 CloudKit SDK 进行网络通信

关键配置与启动流程

初始化配置

框架的初始化需要提供以下配置:

swift
1// 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)

启动流程

  1. SyncEngine 初始化:根据 databaseScope 创建对应的 DatabaseManager 实现(IceCream/Classes/SyncEngine.swift:20-31

  2. 调用 setup():内部调用 databaseManager.prepare() 准备同步环境

  3. 创建自定义区域:调用 createCustomZonesIfAllowed() 在 CloudKit 中创建记录区域

  4. 注册数据库订阅:调用 createDatabaseSubscriptionIfHaveNot() 创建推送订阅

  5. 开始监听变更

    • 调用 startObservingRemoteChanges() 监听云端推送
    • 调用 registerLocalDatabase() 监听本地 Realm 变更
  6. 恢复长生命周期操作:调用 resumeLongLivedOperationIfPossible() 恢复未完成的同步任务

应用生命周期集成

框架需要在 AppDelegateSceneDelegate 中正确集成:

swift
1// 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}

这种集成方式确保了应用在启动时能够恢复同步状态,在收到推送时能够及时拉取云端变更。