【SwiftUI】Realm Swiftとは?導入方法とCRUD処理のやり方
この記事からわかること
- Swiftでデータを永続的に
保存 する方法 - Realmとは?
- Realm Swiftライブラリの導入方法
- Swift UIでの使い方
- CRUD処理のやり方
- Realm構造体のwriteメソッドとは?
index
[open]
\ アプリをリリースしました /
環境
- Xcode:16.3
- iOS:18.4
- Swift:6
- Realm.Swift:20.0.3
- macOS:Sequoia 15.4
iOSアプリに簡単に導入できる「Realmデータベース」についてまとめていきたいと思います。
Realmとは?
Realm(レルム)はiOSやAndroidなどのモバイル向けに開発されたクラスプラットフォームデータベースです。
Realmの特徴はデバイス内にデータベースを作成して使用することです。デバイス自体にデータを保存するためオフライン環境での使用も可能になっており、アプリを停止した場合にもデータが保持されているので再度起動した場合には保存されたデータを使用することが可能になっています。
対応している言語
- Swift
- Objective-C
- Java
- Kotlin
- C#
- JavaScript
またデータベースであることに変わりはないのでテーブル、カラム、レコードといった考え方は同じです。
Realm Swiftライブラリ
SwiftではRealmが「Realm Swift」と呼ばれるライブラリとして提供されています。Swiftではアプリ内からデータを永続的に保存する手段が他にも方法がいくつか存在します。
データを永続的に保存する方法
- テキストファイル
- UserDefaults
- Realm Swift
- KeyChain
それぞれにメリットデメリットがありますが、この中で「Realm Swift」を使う大きなメリットはオリジナルのクラスをそのまま保存できることです。
UserDefaultsではそもそも保存できるデータ型が制限(StringやInt)されており、クラスのまま保存はできません。テキストファイルの場合はJSON形式を使用すれば構造体の保存は可能ですが、操作方法はややこしくコードの見通しも悪くなってしまいます。
またRealm Swiftはデータベース同士のリレーション(関係性)も管理できるのでデータ操作をしやすいのもメリットの1つとなっています。
Realm Swiftの操作方法
Realm Swiftを使用することでデータベース操作(CRUD処理)をSQL文を使用することなくSwiftのクラス(オブジェクト)や配列のように操作できるようになります。
CRUD(クラッド)処理とはデータをデータベースに追加したり、更新したり、削除したりすることを指します。(「Create」、「Read」、「Update」、「Delete」の頭文字)
iOSデバイス内では拡張子が「.realm」とつくファイル名でデータベースが管理されます。このデータベースファイルはアプリがアンインストールされると一緒に削除されます。
Realmの特徴〜まとめ〜
- モバイル向けのデータベース
- Swiftでは「Realm Swift」ライブラリ
- データを永続的に保存可能
- オリジナルのクラスを保存可能
- データベース同士のリレーション(関係性)も管理
- CRUD処理をSwiftのコードで実装
Realm Swiftの導入方法
Realm Swiftはライブラリなので使用するためにはプロジェクトへインポートする必要があります。
ライブラリの追加にはライブラリ管理ツールである「Cocoa Pods(ココアポッズ)」を使用するのがおすすめです。Cocoa PodsがMacに未導入の場合はインストール方法などを下記記事にまとめていますので参考にしてください。
STEP:Realmの導入
- プロジェクトにCocoa Podsを組み込む
- 「PodFile」に「pod 'RealmSwift'」を追記
- ターミナルでpod installを実行
- ファイル内に「import RealmSwift」を追記
- 導入完了!
1.プロジェクトにCocoaPodsを組み込む
ターミナルを起動し以下のコマンドを実行します。
$ cd プロジェクトまでのパス
$ pod init
これでSwiftのプロジェクトファイルにCocoaPodsを組み込むことができました。
2.「PodFile」に「pod 'RealmSwift'」を追記
続いてプロジェクトファイル内に追加された「PodFile」を開き「pod 'RealmSwift'」を追記しておきます。
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target '(プロジェクト名)' do
use_frameworks!
pod 'RealmSwift'
end
3.ターミナルでpod installを実行
再びターミナルに戻りpod installを実行します。以下のように表示されれば成功です。
$ pod install
Analyzing dependencies
Downloading dependencies
Installing Realm (20.0.0)
Installing RealmSwift (20.0.0)
Generating Pods project
Integrating client project
[!] Please close any current Xcode sessions and use `test.xcworkspace` for this project from now on.
Pod installation complete! There is 1 dependency from the Podfile and 2 total pods installed.
[!] Automatically assigning platform `iOS` with version `15.5` on target `test` because no platform was specified. Please specify a platform for this target in your Podfile. See `https://guides.cocoapods.org/syntax/podfile.html#platform`.
4.ファイル内に「import RealmSwift」を追記
CocoaPodsを組み込んでいる場合はXcodeの起動は「プロジェクト名.xcworkspace」から起動します。
あとは使用するファイル内でimport RealmSwiftを追記すればRealmが使用可能になります。
SPMで導入する
SPMで導入したい場合はhttps://github.com/realm/realm-swift.gitで検索して導入します。
https://github.com/realm/realm-swift.git
ただしそのままビルドを試みると以下のようなエラーが発生してビルドできません。
Swift package target 'Realm' is linked as a static library by 'MyAppName' and 'Realm', but cannot be built dynamically because there is a package product with the same name.
この場合は「TARGETS」>「General」>「Frameworks, Libraries, and Embedded Content」に追加されているRealmを消して、RealmSwiftだけに変更し、「Embed & Sign」に設定すれば解消されます。
おすすめ記事:Embed & Signとは?
Swift UIで扱うRealm操作の基本知識
Swift UI内でRealmを使用するにはまず使用するファイルでライブラリをインポートします。これでRealmを使ったデータベース管理が可能になります。
import RealmSwift
RealmではデータベーステーブルをObjectを継承したクラスとして定義します。カラムに該当するのがクラスのプロパティとレコード(データそのもの)に該当するのがクラスインスタンス(オブジェクト)になります。
テーブル(オブジェクト)定義の例
class Shop: Object {
@Persisted var name = ""
@Persisted var menu: Menu?
}
let record = Shop() // レコードを生成する(生成しただけでは保存されい)
| name | menu | |
|---|---|---|
| レコード | Chez Ame | Menuインスタンス1 |
| レコード | Kasa Shop | Menuインスタンス2 |
サポートされているデータ型はBool、Int、Int8、Int16、 Int32、Int64、Double、Float、String、Date、Dataとなっており、そのほかにもObjectを継承した独自のクラスもプロパティに保持することが可能です。
レコードのデータ型
テーブルに格納されたレコードは以下のような配列のようなコレクション形式でオブジェクトが格納されている状態(Results<Element>)として取得できます。なので配列とオブジェクトを操作する感覚でデータに参照することができるようになっています。
結果のデータ型
Results<Element>
例
Results<Shop> <0x14df301a0> (
[0] Shop {
name = Chez Ame;
menu = Menu { // 入れ子になっているRealmオブジェクト
name = Chocolate;
price = 400;
};
}
)
Realm構造体
データベースへのCRUD処理はRealm構造体に定義されている様々なメソッドを使って操作していきます。
@frozen public struct Realm {
// 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
}
なのでまずはRealm構造体のインスタンス化は必須となってきます。例外が発生する可能性があるのでtry!の付与が必要になります。
let realm = try! Realm()
writeメソッド
Realmを使用していく中で重要になってくるのがwriteメソッドです。データベースへの追加や取得、更新、削除などの処理はwriteトランザクションの中で実行する必要があります。
@discardableResult
public func write<Result>(withoutNotifying tokens: [NotificationToken] = [], _ block: (() throws -> Result)) throws -> Result {
// 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
}
// 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
このメソッドもthrowsがついているので実行時にはtryを付与する必要があります。
let realm = try! Realm()
try! realm.write {
realm.add(shop)
}
writeトランザクションの中でオブジェクトを操作することでそのオブジェクトがRealmの管理状態になります。
オブジェクトが管理状態になる
例えば追加処理行うaddメソッドの定義を見てみます。
addメソッドの定義元の解説引用
”追加するオブジェクトは、管理されていないオブジェクトか、このオブジェクトによって既に管理されている有効なオブジェクトである必要があります”
addやデータを取得するobjectsメソッドを使用すると対象のオブジェクトはRealmの管理状態になります。管理状態になるとオブジェクトとテーブルのデータがリンクした状態になりオブジェクトの値の変更がテーブル内にも反映されます。
クラスをコピーするときはデータの受け渡しが値型ではなく、参照型なのでRealm内にクラスのコピーを作成することでリンク状態になるって感じですかね?
値型:その時時点のデータをそのままコピーするだけ
参照型:データが格納されているメモリの参照を渡すので変更が影響し合う
また管理状態になっているオブジェクトはスレッドセーフではなくなります。そのため異なるスレッドで参照しようとするとアプリがクラッシュするので注意してください。
テーブル定義とCRUD処理方法
ポイント
- データベースに保存するデータクラスの定義
- Create:データの保存
- Read:データの取得
- Update:データの更新
- Delete:データの削除
データベーステーブルクラス定義
Realmのデータベースに保存するテーブル情報をクラスとして定義します。定義するクラスはクラスインスタンスをレコードとして操作するためにObjectに準拠させる必要があります。これはRealmSwiftObjectのタイプエイリアスとなっています。
public typealias Object = RealmSwiftObject
またプロパティには@Persistedというプロパティラッパーを付与します。
今回はShopクラスとMenuクラスを定義しておきました。
import RealmSwift
// テーブル
class Shop: Object {
@Persisted var name = "" // カラム
@Persisted var menu: Menu? // カラム
}
// テーブル
class Menu: Object {
@Persisted var name = "" // カラム
@Persisted var price = 0 // カラム
}
この際にShop側に独自のクラスであるMenu型のプロパティを定義しています。ここにはnilを許容できるように?をつけておきます。
nilを許容させないと保存時にエラーになる
エラー:Object property \'menu\' must be marked as optional."
バージョンが古いものは@objc dynamicを付与していた
Realmのバージョンが古いプロジェクトなどをみると@Persistedではなく、@objc dynamicを付与していました。この記法はもう古いようなので@Persistedを使えば問題ないと思います。
class Car: Object {
@objc dynamic var name = ""
@objc dynamic var color = ""
}
おすすめ記事:【Swift UIKit】#selectorとは?使い方と@objcとsender、dynamicの意味まとめ
Create:データの保存
データベースにデータを保存するにはRealm構造体のインスタンスを生成し、writeメソッドを呼び出してその中でaddメソッドを呼び出します。addメソッドの引数に保存したいオブジェクト(レコード)を渡します。
import SwiftUI
import RealmSwift
struct ContentView: View {
var body: some View {
Button {
// レコードの生成
let shop = Shop()
shop.name = "Chez Ame"
let menu = Menu()
menu.name = "Chocolate"
menu.price = 400
shop.menu = menu
// 保存
let realm = try! Realm()
try! realm.write {
realm.add(shop)
}
} label: {
Text("追加")
}
}
}
これで各テーブルが生成されレコードが追加されます。ShopテーブルのレコードにはnameプロパティにChez Ameを、menuプロパティにMenuクラス型の値を保持したShopクラスのインスタンスを格納していますが、データベースの中には以下のようにクラスごとにデータベースが作成され、必要なテーブル同士が関連づけられながら管理されているような形になっています。
Shopデータベース
| name | menu | |
|---|---|---|
| 0 | Chez Ame | Menuテーブル[0] |
Menuデータベース
| name | price | |
|---|---|---|
| 0 | Chocolate | 400 |
この場合保存(add)しているのはShopのみですがプロパティにMenu(別のRealm Object)を保持している場合そのオブジェクトもデータベースに保存されます。なので取得する際にはMenuのみを取得することも可能になります。
Read:データの取得
続いて大元であるShopデータベースの全データを取得してみます。全データを取得するにはobjectsメソッドの引数に取り出したいデータベースクラス名をクラス名.selfと記述します。
クリックで全データを出力するボタン
Button {
let realm = try! Realm()
let shopTable = realm.objects(Shop.self)
print(shopTable)
} label: {
Text("Shopテーブル取得")
}
出力結果を見てみると0番目に先ほど追加したデータが確認できます。menuプロパティの値にはMenuクラスの値がそのまま保存されているのがわかると思います。
結果
Results<Shop> <0x14df301a0> (
[0] Shop {
name = Chez Ame;
menu = Menu {
name = Chocolate;
price = 400;
};
}
)
続いてMenuデータベースを取得してみます。次は決め打ちで0番目のデータを取得してみます。
Button(action: {
let realm = try! Realm()
let menuTable = realm.objects(Menu.self)
print(menuTable[0])
}, label: {
Text("Menu取得")
})
結果
Menu {
name = Chocolate;
price = 400;
}
条件を指定してデータを取得する
コレクション形式で全データが取得できたのでCollection型の持つfilterやRealmCollection型の持つwhereメソッドなどを使って条件を絞ってデータを取得することが可能です。
Button {
let realm = try! Realm()
let shopTable = realm.objects(Shop.self)
if let result = shopTable.where({ $0.menu.price > 300 }).first {
print(result)
} else {
print("データがありません")
}
} label: {
Text("300円以上のShop取得")
}
Update:データの更新
テーブル内のデータを更新するには対象のレコードを取得して更新します。先にデータを更新するコードを見てみます。
Button {
let realm = try! Realm()
let menuRecord = realm.objects(Menu.self).first!
try! realm.write{
menuRecord.price = 1000
}
print(menuRecord)
} label: {
Text("更新")
}
このように取得したレコード情報に対して値を上書きするだけでレコードの値も更新されます。直感的にみるとデータのみを編集しているだけのように見えますが、再度アプリを起動してテーブルの中を参照しても更新された値になっているのを確認してみてください。これはオブジェクトが管理状態になっているためです。
let shop = Shop()
shop.name = "Chez Foo"
let menu = Menu()
menu.name = "Financier"
menu.price = 200
shop.menu = menu
let realm = try! Realm()
try! realm.write {
realm.add(shop)
shop.menu?.price = 300
}
オブジェクトごと更新する
プロパティに値を格納することで対象データを更新していましたが、オブジェクトごと更新することも可能です。オブジェクトごと更新するためには更新対象のオブジェクトと同じIDのオブジェクトを用意してadd(update: .modified)を使用します。
let realm = try! Realm()
// 更新するユーザーのIDを指定する
let userID = 1
let newUser = User()
newUser.id = userID
newUser.name = "New Name"
newUser.age = 30
try! realm.write {
realm.add(newUser, update: .modified)
}
update: .modifiedを指定せずにaddを使用して既にあるIDのオブジェクトを追加しようとした場合はアプリがクラッシュします。
Delete:データの削除
データベースのデータを削除するにはwriteトランザクションの中でdeleteメソッドを使用します。引数には削除したいデータ情報を指定します。例えばShopテーブルを削除したい場合は以下のようになります。
Button {
let realm = try! Realm()
try! realm.write{
let shopTable = realm.objects(Shop.self)
realm.delete(shopTable)
}
} label: {
Text("削除")
}
条件を指定してデータを削除する
条件にマッチしたデータ(レコード)のみを削除する場合はwhereメソッドなどで対象のレコードを取得しdeleteメソッドの引数に渡せばOKです。
Button {
let realm = try! Realm()
let result = shopTable.where({ $0.menu.price > 300 }).first!
try! realm.write{
realm.delete(result)
}
} label: {
Text("削除")
}
テーブルを全て削除する
保存しているテーブルを全て削除したい場合はdeleteAllメソッドを使用します。
let realm = try! Realm()
try! realm.write{
realm.deleteAll()
}
おすすめ記事
【SwiftUI】Realmの@ObservedResultsの使い方!値の観測と解凍
【SwiftUI】Realmでプライマリーキーの設定方法!UUIDを指定
【SwiftUI】Realmのマイグレーション方法!Migration is required due to the following errorsの解決法
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。
私がSwift UI学習に使用した参考書
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。







