【SwiftUI】Realm Swiftとは?導入方法とCRUD処理のやり方

この記事からわかること

  • Swiftデータ永続的保存する方法
  • Realmとは?
  • Realm Swiftライブラリ導入方法
  • Swift UIでの使い方
  • CRUD処理のやり方
  • Realm構造体writeメソッドとは?

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

iOSアプリに簡単に導入できるデータベース「Realm」についてまとめていきたいと思います。

Realmとは?

Realm

Realm(レルム)とはiOSやAndroidなどのモバイル向けに開発されたクラスプラットフォームデータベースです。

Realmの特徴はデバイス内にデータベースを作成して使用することです。デバイス自体にデータを保存するためオフライン環境での使用も可能になっており、アプリを停止した場合にもデータが保持されているので再度起動した場合には保存されたデータを使用することが可能になっています。

対応している言語

またデータベースなのでテーブル、カラム、レコードといった考え方は同じです。

Realm Swiftライブラリ

Realm Swift SDK

SwiftではRealmが「Realm Swift」と呼ばれるライブラリとして提供されています。

Swiftではアプリ内からデータを永続的に保存する手段として使用される「Realm Swift」ですが、他にも方法がいくつか存在します。

データを永続的に保存する方法

それぞれにメリットデメリットがありますが、この中で「Realm Swift」を使う大きなメリットはオリジナルのクラスをそのまま保存できることです。

UserDefaultsではそもそも保存できるデータ型が制限(StringやInt)されており、クラスのまま保存はできません。テキストファイルの場合はJSON形式を使用すれば構造体の保存は可能ですが、操作方法はややこしくコードの見通しも悪くなってしまいます。

またRealm Swiftはデータベース同士のリレーション(関係性)も管理できるのでデータ操作をしやすいのもメリットの1つとなっています。

Realm Swiftの操作方法

Realm Swiftを使用することでデータベース操作(CRUD処理)をSQL文を使用することなくSwiftのクラス(オブジェクト)や配列のように操作できるようになります。

CRUD(クラッド)処理とはデータをデータベースに追加したり、更新したり、削除したりすることを指します。(「Create」、「Read」、「Update」、「Delete」の頭文字)

iOSデバイス内では拡張子が「.realm」とつくファイル名でデータベースが管理されます。このデータベースファイルはアプリがアンインストールされると一緒に削除されます。

Realmの特徴〜まとめ〜

Realm Swiftの導入方法

Realm Swiftはライブラリなので使用するためにはプロジェクトへインポートする必要があります。

ライブラリの追加にはライブラリ管理ツールである「CocoaPods(ココアポッズ)」を使用するのがおすすめです。CocoaPodsがMacに未導入の場合はインストール方法などを下記記事にまとめていますので参考にしてください。

STEP:Realmの導入

  1. プロジェクトにCocoaPodsを組み込む
  2. 「PodFile」に「pod 'RealmSwift'」を追記
  3. ターミナルでpod installを実行
  4. ファイル内に「import RealmSwift」を追記
  5. 導入完了!

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() // レコードを生成する

サポートされているデータ型はBoolIntInt8Int16 Int32Int64DoubleFloatStringDateDataとなっています。

レコードのデータ型

テーブルに格納されたレコードは以下のような配列のようなコレクション形式でオブジェクトが格納されている状態(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処理方法

ポイント

データベーステーブルクラス定義

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型の持つfilterRealmCollection型の持つ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()
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

私がSwift UI学習に使用した参考書

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index