【SwiftUI】Realm Swiftとは?導入方法とCRUD処理のやり方
この記事からわかること
- Swiftでデータを永続的に
保存 する方法 - Realmとは?
- Realm Swiftライブラリの導入方法
- Swift UIでの使い方
- CRUD処理のやり方
- Realm構造体のwriteメソッドとは?
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
iOSアプリに簡単に導入できるデータベース「Realm」についてまとめていきたいと思います。
Realmとは?
Realm(レルム)とはiOSやAndroidなどのモバイル向けに開発されたクラスプラットフォームデータベースです。
Realmの特徴はデバイス内にデータベースを作成して使用することです。デバイス自体にデータを保存するためオフライン環境での使用も可能になっており、アプリを停止した場合にもデータが保持されているので再度起動した場合には保存されたデータを使用することが可能になっています。
対応している言語
- Swift
- Objective-C
- Java
- Kotlin
- C#
- JavaScript
またデータベースなのでテーブル、カラム、レコードといった考え方は同じです。
Realm Swiftライブラリ
SwiftではRealmが「Realm Swift」と呼ばれるライブラリとして提供されています。
Swiftではアプリ内からデータを永続的に保存する手段として使用される「Realm 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はライブラリなので使用するためにはプロジェクトへインポートする必要があります。
ライブラリの追加にはライブラリ管理ツールである「CocoaPods(ココアポッズ)」を使用するのがおすすめです。CocoaPodsがMacに未導入の場合はインストール方法などを下記記事にまとめていますので参考にしてください。
STEP:Realmの導入
- プロジェクトにCocoaPodsを組み込む
- 「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 (10.29.0)
Installing RealmSwift (10.29.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が使用可能になります。
Swift UIで扱うRealm操作の基本知識
Swift UI内でRealmを使用するにはまず使用するファイルでライブラリをインポートします。これでRealmを使ったデータベース管理が可能になります。
import RealmSwift
Realmではデータベーステーブルをクラスとして定義します。カラムに該当するのがクラスのプロパティとレコード(データそのもの)に該当するのがクラスインスタンス(オブジェクト)になります。
カラム1 | カラム2 | |
---|---|---|
レコード | データ1 | データ1 |
レコード | データ2 | データ2 |
Swift内ではオブジェクトを1つのレコードとして操作し、テーブルに追加処理などを行なっていきます。
テーブル(オブジェクト)定義の例
class Shop: Object {
@Persisted var name = ""
@Persisted var menu:Menu?
}
let record = Shop() // レコードを生成する
サポートされているデータ型はBool
、Int
、Int8
、Int16
、 Int32
、Int64
、Double
、Float
、String
、Date
、Data
となっています。
レコードのデータ型
テーブルに格納されたレコードは以下のような配列のようなコレクション形式でオブジェクトが格納されている状態(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(action: {
// レコードの生成
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 |
Read:データの取得
続いて大元であるShopデータベースの全データを取得してみます。全データを取得するにはobjects
メソッドの引数に取り出したいデータベースクラス名をクラス名.self
と記述します。
クリックで全データを出力するボタン
Button(action: {
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(action: {
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(action: {
let realm = try! Realm()
let menuTable = realm.objects(Menu.self).first!
try! realm.write{
menuTable.price = 1000
}
print(menuDB)
}, 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プロパティなどを用意しておきます。
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)
}
更新はadd(update: .modified)
を使用します。
Delete:データの削除
データベースのデータを削除するにはwrite
トランザクションの中でdelete
メソッドを使用します。引数には削除したいデータ情報を指定します。例えばShopテーブルを削除したい場合は以下のようになります。
Button(action: {
let realm = try! Realm()
try! realm.write{
let shopTable = realm.objects(Shop.self)
realm.delete(shopTable)
}
}, label: {
Text("削除")
})
条件を指定してデータを削除する
条件にマッチしたデータ(レコード)のみを削除する場合はwhere
メソッドなどで対象のレコードを取得しdelete
メソッドの引数に渡せばOKです。
Button(action: {
let realm = try! Realm()
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学習に使用した参考書
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。