减少 Realm 文件大小 — Swift SDK
Overview
Realm 文件的大小始终大于其内部存储对象数据的大小。这种架构使 Realm 具备一些出色性能、并发性和安全优势。
Realm 在文件中跟踪的未使用空间内写入新数据。在某些情况下,未使用空间可能占 Realm 文件的一大部分。Realm 的默认行为是自动压缩 Realm 文件,以防文件变得太大。自动压缩无法满足您的使用案例或者您使用的 SDK 版本没有自动压缩功能时,可以使用手动压缩策略。
Realm 文件大小
一般来说,Realm 文件会比同类的 SQLite 数据库更加节省磁盘空间。意外的文件增长可能与 Atlas App Services 引用过时的数据有关。这些因素都会影响文件大小:
锁定的事务
线程
调度队列
当您考虑通过压缩来减小文件大小时,需要记住以下几点:
压缩可能是一项资源密集型操作
压缩可能会阻塞用户界面线程
由于这些因素,您可能不想在每次打开一个域时都对其进行压缩,而是希望在压缩域时考虑 。 这因应用程序的平台和使用模式而异。 在决定何时进行压缩时,请考虑iOS文件大小限制。
避免固定交易
Realm 将读取事务生命周期与 Realm 实例的内存生命周期联系起来。避免“锁定的”旧 Realm 事务。使用自动刷新 Realm,把后台线程中 Realm API 的使用包装在显式自动释放池中。
线程
Realm 会在运行循环迭代开始时更新它访问的数据版本。虽然它可以让您获得一致的数据视图,但它会影响文件大小。
试想以下场景:
线程 A:从 Realm 读取一些数据,然后不让该线程参与处理长时间执行的操作。
线程B:在另一个线程上写入数据。
线程 A:读线程上的版本未更新。Realm 必须保存该数据的中间版本,因而文件大小会随着每次写入而增大。
为避免这一问题,请在域上调用invalidate()。这向域表明,您不再需要到目前为止已经读取过的对象。因此域不必跟踪这些对象的中间版本。当您下次访问时,域将拥有最新版本的对象。
您还可以使用以下两种方法来压缩 Realm:
调度队列
使用 Grand Central Dispatch 访问Realm时 ,您可能会看到类似的文件增长。执行代码时,调度队列的自动释放池可能不会立即耗尽。在调度池释放域对象之前, Realm无法重用数据的中间版本。从调度队列访问域时,请使用显式自动释放池。
自动压缩
版本 10.35.0 新增。
SDK 通过不断重新分配文件内的数据并删除未使用的文件空间,在后台自动压缩 Realm 文件。对于大多数应用程序来说,自动压缩足以最大限度地减小 Realm 文件。
当文件中未使用空间的大小超过文件中用户数据大小的两倍时,即会开始执行自动压缩。仅当文件未被访问时才会执行自动压缩操作。
手动压缩
手动压缩可用于需要更严格管理文件大小的应用程序,或使用不支持自动压缩旧版本 SDK 的应用程序。
Realm 手动压缩的工作原理是:
读取 Realm 文件的全部内容
将内容写入不同位置的新文件
替换原始文件
如果文件包含大量数据,它可能是一项成本高昂的操作。
在域的配置对象上使用 shouldCompactOnLaunch() (Swift) 或 shouldCompactOnLaunch (Objective-C) 来压缩域。指定执行此方法的条件,例如:
磁盘上文件的大小
文件包含多少可用空间
有关方法中执行条件的更多信息,请参阅:使用手动压实的技巧。
重要
可能不会压缩
无论配置设置如何,访问 Realm 时均无法进行压缩。
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.shouldCompactOnLaunch = ^BOOL(NSUInteger totalBytes, NSUInteger usedBytes) { // totalBytes refers to the size of the file on disk in bytes (data + free space) // usedBytes refers to the number of bytes used by data in the file // Compact if the file is over 100MB in size and less than 50% 'used' NSUInteger oneHundredMB = 100 * 1024 * 1024; return (totalBytes > oneHundredMB) && ((double)usedBytes / totalBytes) < 0.5; }; NSError *error = nil; // Realm is compacted on the first open if the configuration block conditions were met. RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error]; if (error) { // handle error compacting or opening Realm }
let config = Realm.Configuration(shouldCompactOnLaunch: { totalBytes, usedBytes in // totalBytes refers to the size of the file on disk in bytes (data + free space) // usedBytes refers to the number of bytes used by data in the file // Compact if the file is over 100MB in size and less than 50% 'used' let oneHundredMB = 100 * 1024 * 1024 return (totalBytes > oneHundredMB) && (Double(usedBytes) / Double(totalBytes)) < 0.5 }) do { // Realm is compacted on the first open if the configuration block conditions were met. let realm = try Realm(configuration: config) } catch { // handle error compacting or opening Realm }
异步压缩 Realm
使用 Swift async/await 语法异步打开 Realm 时,可以在后台压缩 Realm。
func testAsyncCompact() async { let config = Realm.Configuration(shouldCompactOnLaunch: { totalBytes, usedBytes in // totalBytes refers to the size of the file on disk in bytes (data + free space) // usedBytes refers to the number of bytes used by data in the file // Compact if the file is over 100MB in size and less than 50% 'used' let oneHundredMB = 100 * 1024 * 1024 return (totalBytes > oneHundredMB) && (Double(usedBytes) / Double(totalBytes)) < 0.5 }) do { // Realm is compacted asynchronously on the first open if the // configuration block conditions were met. let realm = try await Realm(configuration: config) } catch { // handle error compacting or opening Realm } }
从 Realm Swift SDK 10.15.0 和 10.16.0 版本开始,很多 Realm API 均支持 Swift“异步/等待”语法。项目必须符合以下要求:
Swift SDK 版本 | Swift 版本要求 | 支持的操作系统 |
---|---|---|
10.25.0 | Swift 5.6 | iOS 13.x |
10.15.0 或 10.16.0 | Swift 5.5 | iOS 15.x |
如果您的应用会在 async/await
上下文中访问 Realm,请使用 @MainActor
来标记此代码,从而避免出现与线程相关的崩溃。
制作压缩副本
您可以使用 Realm .writeCopy(toFile:encryptionKey:) 将域的压缩(和可选 加密 )副本保存到另一个文件位置。方法。 目标文件不能已存在。
重要
避免在写事务中调用此方法。此方法如果在写事务中调用,则会复制绝对最新的数据。它包括您在调用此方法之前在事务中所做的任何未提交的更改。
手动压缩的技巧
压缩 Realm 可能是一项代价高昂的操作,可能会阻塞用户界面线程。应用程序不应在每次打开 Realm 时都执行压缩操作。相反,应尝试优化压缩,使应用程序能够定期执行压缩操作,防止文件过大。如果您的应用程序在资源有限的环境下运行,您可能需要在文件达到特定大小或文件大小对性能产生负面影响时进行压缩。
这些建议可以帮助您优化应用程序的手动压缩:
将最大文件大小设置为平均 Realm 状态大小的倍数。如果平均 Realm 状态大小为 10MB,您可以将最大文件大小设置为 20MB 或 40MB,具体取决于预期的使用情况和设备限制。
首先,超过 50% 的 Realm 文件大小不再使用时,即可压缩 Realm。将当前已使用的字节数除以总文件大小,确定当前使用的空间百分比。然后,检查该值是否小于 50%。也就是说,超过 50% 的 Realm 文件大小是未使用的空间。这正是压缩的好时机。经过试验后,您可能会发现最适合您的应用程序的百分比并不相同。
在您的 shouldCompactOnLaunch
回调中,这些计算可能如下所示:
// Set a maxFileSize equal to 20MB in bytes let maxFileSize = 20 * 1024 * 1024 // Check for the realm file size to be greater than the max file size, // and the amount of bytes currently used to be less than 50% of the // total realm file size return (realmFileSizeInBytes > maxFileSize) && (Double(usedBytes) / Double(realmFileSizeInBytes)) < 0.5
测试各种条件,确定在应用程序中压缩 Realm 文件的频率,以实现平衡。
考虑 iOS 文件大小限制
大型 Realm 文件可能会影响应用程序的性能和可靠性。任何单个 Realm 文件都不能大于 iOS 中允许应用程序映射的内存量。此限制取决于设备以及当时内存空间的碎片化程度。
如果需要存储更多数据,可将其映射到多个 Realm 文件中。
总结
Realm 的架构提供了与线程相关的优势,但可能会导致文件变大。
自动压缩功能在文件未被访问时可以解决文件增大的问题。
当自动压缩不能满足应用程序需求时,可以使用
shouldCompactOnLaunch()
等手动压缩策略。如果有其他进程正在访问 Realm,则无法进行压缩。
使用异步/等待语法时,可以在背景压缩 Realm。