配置和打开同步 Realm — Swift SDK
在此页面上
同步 Realm
您可以配置 Realm,以在许多设备之间自动同步数据,并且每个设备都有自己的本地数据副本。同步 Realm 使用与纯本地 Realm 不同的配置,并且需要 Atlas App Services 后端处理同步过程。
应用程序始终可以在本地创建、修改和删除同步的域对象,即使在离线状态下也是如此。 只要网络连接可用, Realm 软件开发工具包(Realm SDK)就会建立与应用程序服务器的连接,并与其他客户端进行双向更改同步。 Atlas Device Sync协议和服务器端操作转换可确保域的所有完全同步实例看到完全相同的数据,即使某些更改发生在离线状态和/或接收时无序。
已同步 Realms 与非同步 Realms
同步 Realm 与非同步本地 Realm 有几个不同之处:
同步 Realm 尝试将更改与后端 App Services App 同步,而非同步 Realm 则不会。
同步 Realm 可以由经过身份验证的用户访问,而非同步 Realm 没有用户或身份验证的概念。
使用同步 Realm,可以指定下载行为,在打开域之前下载更新。但是,要求在打开域之前下载更改需要用户处于在线状态。非同步域始终可以离线使用。
您可以将数据从非同步域复制到同步域,反之亦然,但不能同步非同步域。 要将非同步域转换为同步域,您可以按照将仅限本地的应用程序迁移到启用同步的应用程序中描述的进程进行操作。
有关如何配置和打开非同步域的更多信息,请参阅配置和打开 Realm - Swift SDK。
打开同步 Realm
打开同步 Realm 的典型流程包括:
创建同步配置。
使用配置打开用户的已同步 Realm。
在身份验证时,我们将用户凭证缓存在设备上的 sync_metadata.realm
文件中。
当您在身份验证后打开同步 Realm 时,您可以跳过登录流程,并利用之前创建的相同同步配置直接打开同步 Realm。
使用缓存的凭证,可以:
立即使用设备上的数据打开同步 Realm。您可以离线或在线使用此方法。
从应用程序下载更改后,打开同步 Realm。这要求用户具备有效的互联网连接。
当您选择灵活同步时,请使用 flexibleSyncConfiguration()
打开同步 Realm。
提示
如果您的应用会在 async/await
上下文中访问 Realm,请使用 @MainActor
来标记此代码,从而避免出现与线程相关的崩溃。
let realm = try await openFlexibleSyncRealm() // Opening a realm and accessing it must be done from the same thread. // Marking this function as `@MainActor` avoids threading-related issues. func openFlexibleSyncRealm() async throws -> Realm { let app = App(id: APPID) let credentials = emailPasswordCredentials(app: app) let user = try await app.login(credentials: credentials) var config = user.flexibleSyncConfiguration() // Pass object types to the Flexible Sync configuration // as a temporary workaround for not being able to add complete schema // for a Flexible Sync app config.objectTypes = [Task.self, Team.self] let realm = try await Realm(configuration: config, downloadBeforeOpen: .always) print("Successfully opened realm: \(realm)") return realm }
重要
Flexible Sync 需要订阅
在内存中打开同步 Realm
10.46.0 版本新增功能。
同步数据库可以完全在内存中打开,这样就不会创建 .realm
文件或其相关的辅助文件。相反,SDK 会在 Realm 处于打开状态时将对象存储在内存中,并在所有实例关闭后立即丢弃它们。
要在内存中打开同步域,请将域配置的 inMemoryIdentifier 属性设置为字符串标识符。
// Instantiate the app and get a user. let app = App(id: APPID) let user = try await app.login(credentials: Credentials.anonymous) // Create a configuration. var configuration = user.flexibleSyncConfiguration() configuration.objectTypes = [Task.self, Team.self] // Specify an in-memory identifier for the configuration. configuration.inMemoryIdentifier = "YOUR-IDENTIFIER-STRING" // Open a Realm with this configuration. let realm = try await Realm(configuration: configuration) print("Successfully opened realm: \(realm)") // Add subscriptions and work with the realm
以不同的 Sync 用户身份打开同步 Realm
版本 10.23.0 中的新增功能。
如果您想以不同同步用户的身份打开同步域,则可以使用 WriteCopy (configuration:) 方法创建一个同步域的副本,以便应用于新用户的同步配置。以下示例展示了如何创建一个同步域的副本,同时保留所有现有数据,以便与不同的同步配置一起使用。
在复制了新同步用户配置的 Realm 之后,您可以将该副本作为该用户的同步 Realm 来打开。
注意
仅限同类型同步
此方法仅支持复制另一个基于分区的同步用户的基于分区的同步配置,或复制另一个 Flexible Sync 用户的 Flexible Sync 配置。 您不能使用此方法在基于分区的同步 Realm 和 Flexible Sync Realm 之间进行转换,反之亦然。
提示
如果您的应用会在 async/await
上下文中访问 Realm,请使用 @MainActor
来标记此代码,从而避免出现与线程相关的崩溃。
try await convertSyncedRealmForAnotherUser() // Opening a realm and accessing it must be done from the same thread. // Marking this function as `@MainActor` avoids threading-related issues. func convertSyncedRealmForAnotherUser() async throws { let app = App(id: YOUR_APP_SERVICES_APP_ID) // Log in the user whose realm you want to use with another sync user let frodoBaggins = try await app.login(credentials: Credentials.anonymous) var frodoConfig = frodoBaggins.configuration(partitionValue: "Some Partition Value") frodoConfig.objectTypes = [QsTask.self] // Open the synced realm, and confirm it contains the data we want // the other user to be able to access. let frodoRealm = try await Realm(configuration: frodoConfig, downloadBeforeOpen: .always) let frodoRealmTasks = frodoRealm.objects(QsTask.self) let frodoSyncedTasks = frodoRealmTasks.where { $0.owner == "Frodo" } XCTAssertEqual(frodoSyncedTasks.count, 3) print("Successfully opened frodo's realm and it contains this many tasks: \(frodoSyncedTasks.count)") // Log in as the user who will work with frodo's synced realm let samwiseGamgee = try await app.login(credentials: Credentials.anonymous) var samConfig = samwiseGamgee.configuration(partitionValue: "Some Partition Value") samConfig.objectTypes = [QsTask.self] // Specify an output directory for the copied realm // We're using FileManager here for tested code examples. guard let outputDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return } // Append a file name to complete the path let copiedRealmFilePath = outputDir.appendingPathComponent("copied.realm") // Update the config file path to the path where you want to save the copied realm samConfig.fileURL = copiedRealmFilePath // Make a copy of frodo's realm that uses sam's config try frodoRealm.writeCopy(configuration: samConfig) // Open sam's realm, and see that it contains the same data as frodo's realm let samRealm = try await Realm(configuration: samConfig) let samRealmTasks = samRealm.objects(QsTask.self) var samSyncedTasks = samRealmTasks.where { $0.owner == "Frodo" } print("Successfully opened sam's realm and it contains this many tasks: \(samSyncedTasks.count)") XCTAssertEqual(frodoSyncedTasks.count, samSyncedTasks.count) // Add a task to sam's realm let task = QsTask(value: ["name": "Keep an eye on that Gollum", "owner": "Sam"]) try! samRealm.write { samRealm.add(task) } // See that the new task reflects in sam's realm, but not frodo's samSyncedTasks = samRealmTasks.where { $0.owner == "Sam" } XCTAssertEqual(samSyncedTasks.count, 1) let samTasksInFrodoRealm = frodoRealmTasks.where { $0.owner == "Sam" } XCTAssertEqual(samTasksInFrodoRealm.count, 0) }
使用 Swift 并发功能打开同步 Realm
您可以使用 Swift 并发功能将同步 Realm 打开为角色隔离的 Realm:
func mainThreadFunction() async throws { // Initialize the app client and authenticate a user let app = App(id: APPID) let user = try await app.login(credentials: Credentials.anonymous) // Configure the synced realm var flexSyncConfig = user.flexibleSyncConfiguration(initialSubscriptions: { subs in subs.append(QuerySubscription<Todo>(name: "all_todos"))}) flexSyncConfig.objectTypes = [Todo.self] // Open and use the synced realm let realm = try await Realm(configuration: flexSyncConfig, actor: MainActor.shared, downloadBeforeOpen: .always) try await useTheSyncedRealm(realm: realm) }
有关使用 actor 隔离的 Realm 的更多信息,请参阅结合使用 Realm 与 Actor - Swift SDK。
打开前下载更改
版本 10.15.0 新增内容。
当您使用 Swift SDK 打开已同步 Realm 时,可传递 downloadBeforeOpen
参数来指定在打开该 Realm 之前是否从应用程序下载变更集。此参数接受来自 OpenBehavior
枚举中的某一情况:
never
:立即打开设备上的 Realm。用户有网时在后台下载更改,但不要阻止打开 Realm。always
:每次打开 Realm 时检查是否有更改。要求用户必须有有效的互联网连接。once
:首次打开 Realm 前先下载数据,但在后续打开时仅打开 Realm 而不下载更改。这样您就可以使用初始数据填充 Realm,但在后续打开时启用离线优先功能。
提示
如果您的应用会在 async/await
上下文中访问 Realm,请使用 @MainActor
来标记此代码,从而避免出现与线程相关的崩溃。
func getRealmAfterDownloadingUpdates() async throws -> Realm { let app = App(id: APPID) let user = try await app.login(credentials: Credentials.anonymous) var configuration = user.flexibleSyncConfiguration() configuration.objectTypes = [FlexibleSync_Task.self, FlexibleSync_Team.self] let realm = try await Realm(configuration: configuration, downloadBeforeOpen: .always) print("Successfully opened realm after downloading: \(realm)") return realm } let realm = try await getRealmAfterDownloadingUpdates() print("The open realm is: \(realm)") // Add subscription and work with the realm
离线打开同步 Realm
当您的 Realm 应用程序对用户进行身份验证时,它会缓存该用户的档案。您可以检查是否存在现有用户档案以绕过登录流程并访问已缓存的用户。使用它可离线打开一个 Realm。
注意
初始登录需要网络连接
当用户注册您的应用或使用客户端上的现有帐户首次登录时,客户端必须具有网络连接。通过检查是否存在已缓存的用户档案,您可以离线打开 Realm,但前提是用户之前已在线登录。
仅当您不要求客户端应用程序在打开 Realm 之前always
下载更改时,才能离线打开该 Realm。
提示
如果您的应用会在 async/await
上下文中访问 Realm,请使用 @MainActor
来标记此代码,从而避免出现与线程相关的崩溃。
// Log the user into the backend app. // The first time you login, the user must have a network connection. func getUser() async throws -> User { // Check for an existing user. // If the user is offline but credentials are // cached, this returns the existing user. if let user = app.currentUser { return user } else { // If the device has no cached user // credentials, log them in. let app = App(id: YOUR_APP_SERVICES_APP_ID) let loggedInUser = try await app.login(credentials: Credentials.anonymous) return loggedInUser } } let user = try await getUser() var configuration = user.configuration(partitionValue: "Some Partition Value") // Open a Realm with this configuration. // If you do not require the app to download updates // before opening the realm, the realm just opens, even if // offline. let realm = try await Realm(configuration: configuration) print("Successfully opened realm: \(realm)")