iOS 開発でローカルでのデータ永続化を行うための選択肢として、UserDefaults
やRealm
がよく挙げられると思います。Realm
は高性能なローカルストレージで売り出されているので、高速での読み書きを行いたい場合などはよく使われていると思っています。
ただ、WWDC23 でSwiftData
という Apple 純正の高機能ローカルストレージライブラリが登場したことで、高性能なローカルストレージの新たな選択肢として、SwiftData
も挙がるようになりました。
今まで私は、業務ではもちろん、個人開発する時もRealm
をよく使っていましたが、SwiftData
もいつか使ってみたいなーとか思っていました。
そんな中あるいつものように Realm を使っていると、ちょっとした違和感を感じました。
みなさんも、Realm を使っていてこんな経験はないでしょうか?
違和感
ビルド遅くない?
Preview の表示遅すぎ
今まで Realm 以外にもたくさんのライブラリに依存したアプリ開発が多かったので、たくさんのライブラリに依存しているからそりゃ遅くなるよねっと考えていました。
ただ、最近ビルド速度を計測する方法を覚えたので、個人アプリのビルド速度を測ってみたら驚きの事実が判明しました。
ビルドのタイムラインを見てみると、ビルドプロセスのほぼ半分は Realm 関連のコードのコンパイルに使われていたのです。(以下赤枠部分)
この結果を見ると、今まで Realm に依存しているプロジェクトが何だかビルド時間遅い気がしていたのは、Realm によるところが大きいのでは?と思えてきました。
仮説
Realm
からSwiftData
に移行したら、ビルド速度爆速になるのでは?
試してみる
検証方法
SPM で、以下構成のサンプルプロジェクトを作って、検証してみます。
それぞれの要素を一つのモジュールと捉えてください。
簡単に構成の説明をすると、RealmClient
はRealmSwift
に依存したアプリケーションを実装するモジュールで、このモジュールをビルドすると、Realm
を含んだアプリをビルドすることになります。
SwiftDataClient
はSwiftData
に依存したモジュールで、ビルドするとSwiftData
を含めてビルドすることになります。
両方で同じUI
に依存しているので、それぞれのモジュールで違うのは、どのローカルストレージライブラリに依存しているかだけです。
つまり、RealmClient
とSwiftDataClient
のビルド速度を比較することで、今回の仮説を検証できます。
検証結果
結果は以下の通りで、SwiftDataClient
のビルド速度が圧倒的に早かったです。
モジュール | 依存ライブラリ | ビルド速度 |
---|---|---|
RealmClient | Realm | 82.5s |
SwiftDataClient | SwiftData | 2.6s |
仮説②
@externvoid@github (extern void)さんのコメントを受けて、先ほどの検証は同じ条件ではなくビルド速度の比較方法として適切でなさそうということに気づきました。(コメントありがとうございます!!)
SwiftData
は標準のSDKにコンパイル済みのバイナリとして同梱されているので、ビルド時にコンパイルは行わないのに対して、Realm
はSPMでソースコードをチェックアウトしているだけでビルド時にはコンパイルから行わないといけないので、SwiftData
の方が圧倒的にビルド速度が速くなることは自明でありました。
そのため、同じ条件で(両ライブラリをコンパイル済みのバイナリにして)RealmClient
とSwiftDataClient
のビルド速度を比較すると、ほとんど変わらないのではないか?
というコメントをいただきましたので、仮説②として確認してみました!
検証方法
SPMでRealm
に依存するときに、GithubリポジトリのURLを使うのではなく、XCFrameworkを取得して、binaryTarget
としてRealm
に依存するようにします。
XCFrameworkは、リポジトリのリリースノートからCarthage用のXCFrameworkを拝借しました。
ダウンロードしたzipを展開して、Realm.xcframework
とRealmSwift.xcframework
をPackage.swift
でbinaryTarget
として定義して、依存するようにします。
Package.swift
// swift-tools-version: 6.1
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: “LocalStoragePeformanceExample”,
platforms: [.iOS(.v18), .macOS(.v15)],
products: [
.library(
name: “SampleUI”,
targets: [“SampleUI”]
),
.library(
name: “RealmClient”,
targets: [“RealmClient”]
),
.library(
name: “SwiftDataClient”,
targets: [“SwiftDataClient”]
)
],
targets: [
.target(
name: “SampleUI”
),
.target(
name: “RealmClient”,
dependencies: [
“SampleUI”,
“RealmModel”
]
),
.target(
name: “SwiftDataClient”,
dependencies: [
“SampleUI”,
“SwiftDataModel”
]
),
.target(
name: “RealmModel”,
dependencies: [
“RealmSwiftBinary”,
“RealmBinary”
]
),
.binaryTarget(
name: “RealmSwiftBinary”,
path: “RealmSwift.xcframework”
),
.binaryTarget(
name: “RealmBinary”,
path: “Realm.xcframework”
),
.target(name: “SwiftDataModel”)
]
)
上記手順で、Realm
をXCFrameworkとして依存することで、コンパイルせずにビルドできるようになるので、SwiftData
と同じ条件でビルド速度を比較できるようになります。
検証結果
モジュール | 依存ライブラリ | ビルド速度 |
---|---|---|
RealmClient | Realm | 1.9s |
SwiftDataClient | SwiftData | 2.6s |
仮説②の通り、ビルド速度はほぼ同じになりました!!
感想
結果で見た通り、Realm
もコンパイル済みのバイナリを扱うようにすれば、ビルド速度のボトルネックは大幅に解消できそうです。
今回の手順では、XCFrameworkをリポジトリに含める必要があるので、リポジトリのサイズが大きくなってしまう懸念がありますが(別の方法あったら教えてもらえると嬉しいです)
それでもビルド速度の面で受けるメリットはかなり大きいので、このアプローチでビルド速度解消できるケースも多そうと思いました。
ビルド速度だけみると、SwiftData
の方が圧倒的で驚きでした。多少早くなるだろうとはおもってたけど、まさかここまで差が出るとは思わなかったです。
ただ、ローカルストレージライブラリとしてのパフォーマンス面で比べると
大量のデータに対しての読み書きなどはRealm
の方が上なので、
パフォーマンス重視のプロダクトにはRealm
を使うといった判断はできそうですね。
パフォーマンスとビルド速度はトレードオフになりそうなので、そこを考慮して今後はローカルストレージのライブラリ選定をしたいです。
工夫すれば、Realm
/SwiftData
に依存したプロジェクトのビルド速度はほぼ同じなので、
どちらを使うかを検討する時は、ライブラリの機能面やパフォーマンス面を重視する方が良さそうと思いました。
Views: 0