捆绑 Realm 文件 - Swift SDK
注意
捆绑同步 Realm
Swift SDK 版本10.23.0 引入了捆绑同步 Realm 的功能。 在版本10.23.0之前, 您只能捆绑本地 Realm。
Realm 支持捆绑Realm 文件。 捆绑 Realm 文件时,您将在应用程序下载中包含一个数据库及其所有数据。
这允许用户使用一组初始数据首次启动应用程序。对于同步 Realm,捆绑可以避免用户首次打开应用程序时长时间的初始下载。相反,用户只需下载自生成捆绑文件以来发生的同步更改。
重要
捆绑同步 Realm
如果您的后端应用程序使用 Flexible Sync ,则用户在首次打开捆绑 Realm 文件时可能会遇到客户端重置。启用客户端最大离线时间(默认情况下启用客户端最大离线时间)时,可能会出现这种情况。如果在用户首次同步之前生成捆绑 Realm 文件的时间超过了客户端最大离线时间设置指定的天数,则用户会遇到客户端重置。
执行客户端重置的应用程序会从应用程序后端下载 Realm 的完整状态。 这抵消了捆绑 Realm 文件的优势。 为防止客户端重置并保留 Realm 文件捆绑的优点,请执行以下操作:
避免在捆绑同步 Realm 的应用程序中使用客户端最大离线时间。
如果您的应用程序确实使用了客户端最大离线时间,请确保您的应用程序下载始终包含最近同步的 Realm 文件。 为每个应用程序版本生成一个新文件,并确保任何版本保持最新状态的时间都不会超过客户端最大离线时间天数。
概述
要创建 Realm 文件并将其与应用程序捆绑在一起,请执行以下操作:
创建一个 Realm 文件,其中包含要捆绑的数据。
将 Realm 文件捆绑到生产应用程序中。
在生产应用程序中,从捆绑的资产文件中打开 Realm 。 对于同步 Realm,您必须提供分区键。
创建用于捆绑的 Realm 文件
构建一个共享应用程序数据模型的临时 Realm 应用。
打开一个 Realm 并添加要捆绑的数据。 如果使用同步 Realm,请留出时间让 Realm 完全同步。
使用writeCopy(configuration:)方法将 Realm 复制到新文件中:
提示
如果您的应用会在 async/await
上下文中访问 Realm,请使用 @MainActor
来标记此代码,从而避免出现与线程相关的崩溃。
try await createBundledRealm() // Opening a realm and accessing it must be done from the same thread. // Marking this function as `@MainActor` avoids threading-related issues. func createBundledRealm() async throws { let app = App(id: YOUR_APP_SERVICES_APP_ID) // Log in the user whose realm you want to copy for bundling let seedUser = try await app.login(credentials: Credentials.anonymous) // Create a configuration to open the seed user's realm var config = seedUser.configuration(partitionValue: "Partition You Want to Bundle") config.objectTypes = [Todo.self] // Open the realm with the seed user's config let realm = try await Realm(configuration: config, downloadBeforeOpen: .always) print("Successfully opened realm: \(realm)") // Verify there is a todo object in the realm whose // owner's name is "Daenerys". When we open the bundled // realm later, we should see the same result. let todos = realm.objects(Todo.self) let daenerysTodos = todos.where { $0.owner == "Daenerys" } XCTAssertEqual(daenerysTodos.count, 1) // Specify an output directory for the bundled realm // We're using FileManager here for tested code examples, // but this could be a static directory on your computer. guard let outputDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return } // Append a file name to complete the path let bundleRealmFilePath = outputDir.appendingPathComponent("seed.realm") // Update the config file path to the path where you want to save the bundled realm config.fileURL = bundleRealmFilePath // Check to see if there is already a realm at the bundled realm file path. If there // is already a realm there, delete it. if Realm.fileExists(for: config) { try Realm.deleteFiles(for: config) print("Successfully deleted existing realm at path: \(bundleRealmFilePath)") } else { print("No file currently exists at path") } // Write a copy of the realm at the URL we specified try realm.writeCopy(configuration: config) // Verify that we successfully made a copy of the realm XCTAssert(FileManager.default.fileExists(atPath: bundleRealmFilePath.path)) print("Successfully made a copy of the realm at path: \(bundleRealmFilePath)") // Verify that opening the realm at the new file URL works. // Don't download changes, because this can mask a copy // that does not contain the expected data. let copiedRealm = try await Realm(configuration: config, downloadBeforeOpen: .never) print("Successfully opened realm: \(realm)") // Verify that the copied realm contains the data we expect let copiedTodos = copiedRealm.objects(Todo.self) let daenerysCopiedTodos = copiedTodos.where { $0.owner == "Daenerys" } XCTAssertEqual(daenerysCopiedTodos.count, 1) print("Copied realm opens and contains this many tasks: \(daenerysCopiedTodos.count)") }
writeCopy(configuration: )在复制之前会自动将 Realm 压缩到尽可能小的大小。
注意
同步 Realm 和仅限本地 Realm 之间的区别
上述示例使用SyncConfiguration
来配置同步 Realm。 要创建本地 Realm 的副本,请改为使用RealmConfiguration
配置您的 Realm。
在生产应用程序中捆绑 Realm 文件
现在您已拥有包含初始数据的 Realm 副本,请将其与生产应用程序捆绑在一起。 从广义上讲,这需要:
使用与生产应用程序完全相同的数据模型创建一个新项目。 打开一个 Realm 并添加要捆绑的数据。 由于 Realm 文件是跨平台的,因此您可以在 macOS 应用程序中执行此操作。
将 Realm 文件的压缩副本拖到生产应用程序的 Xcode 项目导航器中。
在 Xcode 中转到应用程序目标的 Build Phases标签页。 将 Realm 文件添加到Copy Bundle Resources构建阶段。
此时,您的应用可以访问捆绑的 Realm 文件。 使用 Bundle.main.path(forResource:ofType) 查找其路径。
如果Realm.Configuration上的readOnly
属性设置为true
,则可以直接在捆绑包路径打开 Realm。 如果要修改捆绑 Realm,请先将捆绑文件复制到应用程序的“文档”文件夹中,并在配置中设置seedFilePath
和捆绑 Realm 的 URL。
提示
请参阅 迁移示例应用 对于使用捆绑的本地 Realm 的完整运行应用程序。
从捆绑的 Realm 文件中打开 Realm
现在您已经拥有生产应用程序中包含的 Realm 副本,您需要添加代码来使用它。 在配置 Realm 以从捆绑文件中打开 Realm 时,请使用SEEDFilePath方法:
提示
如果您的应用会在 async/await
上下文中访问 Realm,请使用 @MainActor
来标记此代码,从而避免出现与线程相关的崩溃。
try await openBundledSyncedRealm() // Opening a realm and accessing it must be done from the same thread. // Marking this function as `@MainActor` avoids threading-related issues. func openBundledSyncedRealm() async throws { let app = App(id: YOUR_APP_SERVICES_APP_ID) // Log in an app user who will use the bundled realm let user = try await app.login(credentials: Credentials.anonymous) // Create a configuration for the app user's realm // This should use the same partition value as the bundled realm var newUserConfig = user.configuration(partitionValue: "Partition You Want to Bundle") newUserConfig.objectTypes = [Todo.self] // Find the path of the seed.realm file in your project let realmURL = Bundle.main.url(forResource: "seed", withExtension: ".realm") print("The bundled realm URL is: \(realmURL)") // When you use the `seedFilePath` parameter, this copies the // realm at the specified path for use with the user's config newUserConfig.seedFilePath = realmURL // Open the synced realm, downloading any changes before opening it. // This starts with the existing data in the bundled realm, but checks // for any updates to the data before opening it in your application. let realm = try await Realm(configuration: newUserConfig, downloadBeforeOpen: .always) print("Successfully opened the bundled realm") // Read and write to the bundled realm as normal let todos = realm.objects(Todo.self) // There should be one todo whose owner is Daenerys because that's // what was in the bundled realm. var daenerysTodos = todos.where { $0.owner == "Daenerys" } XCTAssertEqual(daenerysTodos.count, 1) print("The bundled realm has \(daenerysTodos.count) todos whose owner is Daenerys") // Write as usual to the realm, and see the object count increment let todo = Todo(value: ["name": "Banish Ser Jorah", "owner": "Daenerys", "status": "In Progress"]) try realm.write { realm.add(todo) } print("Successfully added a todo to the realm") daenerysTodos = todos.where { $0.owner == "Daenerys" } XCTAssertEqual(daenerysTodos.count, 2) }
注意
同步 Realm 和仅限本地 Realm 之间的区别
上述示例使用SyncConfiguration
来配置同步 Realm。 要创建本地 Realm 的副本,请改为使用Realm.Configuration
配置您的 Realm。