くま's Tech系Blog

基本的には技術で学んだことを書き留めようと思います。雑談もやるかもね!

Realmを使ってみよう(iOS編)

だいぶ久しぶりの更新になりました(なかなか忙しくて...)

今年初の更新がこのタイミングになってしまいましたが、これからは定期的に更新できればと思います

今回はRealmに関してまとめます。今回はiOS編です

Realmとは?

まずはRealmについて軽く説明しようと思います

RealmのGithubには下記のように説明があります

Realm is a mobile database that runs directly inside phones, tablets or wearables.

簡単にいうと、Realmというのは端末に保存するDBのようなものです

一般的にはデータを保存する場合にはAPIを使ってサーバー側のDBに保存しますが、その必要なく、端末のデータを保存して端末からデータを取得できるようになります

また特徴は下記のように羅列されています

Intuitive to Developers: Realm’s object-oriented data model is simple to learn, doesn’t need an ORM, and lets you write less code.

Designed for Offline Use: Realm’s local database persists data on-disk, so apps work as well offline as they do online.

Built for Mobile: Realm is fully-featured, lightweight, and efficiently uses memory, disk space, and battery life.

大まかな特徴としては学習コストが低い、オフラインでも使える、パフォーマンスがいいなどが挙げられています

パフォーマンスは実際に使ってもらえるとわかると思うですが、インディケーター(処理中のグルグル)が要らないのではないかというくらいすぐデータを取得・登録できます

次からはRealmSwiftに見ていきましょう

RealmSwiftの導入

iOSでRealmを使う場合にはrealmSwiftというオープンソースのライブラリがあるので使っていきます

podで導入する場合には下記podファイルに追加してインストールするという他のライブラリと同じ方法で導入できます。この辺りの詳しい説明は書略します

pod 'RealmSwift'

Realmを使ってみる

ここから使い方を説明しようと思います。まずはModelの作成からです

Modelの作成

ModelはDBでいうところのテーブルやカラムにあたります

今回は仮でユーザーを表すクラスを作成します

import Foundation
import RealmSwift

class User: Object {
    @persisted(primaryKey:true)  var id: Int = 0
    @persisted  var name: String?
    @persisted  var age: Int?
}

まず、モデル定義をするにはObjectを継承したクラスを作成する必要があります

今回はUserクラスを作成しました。ここでUserというテーブルが作成されるイメージです

さらに、変数の前に@persistedを追加することでカラムとして扱われます。逆にいうと、@persistedがない変数に関してはRealmには登録されないので、うまく使い分けることができます

idに(primaryKey:true)とつけているのはidをプライマリーキーとして扱うようにするためです。プライマリーキーがなぜ必要かというのは後述します

Realmの初期化(起動)

Modelを初期化した後にはRealmインスタンスを初期化してDBを起動します

let realm = try! Realm()

これがよくみるRealm起動の処理だと思います

初期化についてはもう少し詳しくあとで説明したいと思いますが、Realmを起動する場合にはこの処理でできます

RealmのCRUD

次にRealmの登録・読み取り・更新・削除についてです

読み取り

まずは読み取りです

先ほど初期化したRealmを使って読み取りを行います

val realmRegistedData = realm.objects(User.self)

これでRealmに登録されているUserクラスのデータを全件取得できます

条件を指定する場合にはwhereを追加すれば特定の条件のデータを取得できます

// 名前が「テスト」のデータのみ取得
let realmFilterNameData = realm.objects(User.self).where { $0.name = "テスト"}

上記のrealmFilterNameDataResult型のためArrayなどに変換する場合には下記のようにConvertする必要があります

var userConverted = [User]()

val realmFilterNameData = realm.objects(User.self).where { $0.name = "テスト"}

for data in realmFilterNameData {
    userConverted.append(User(value: data))
}
登録・更新

登録と更新は同じ処理でできますのでまとめます

let user = User()
user.id = 1
user.name = "テスト"
user.age = 2

try? realm.write {
    realm.add(user, update: .modified)
}

登録したいUserのインスタンスを作成してaddするだけです

重要なのは読み取りと違って、realm.writeというトランザクションの中で行わないと登録されません

更新に関してはrealm.add(user, update: .modified)update: .modifiedの部分で更新処理を行います

どういうことかというとprimaryKeyが一致しているデータがRealmに登録されている場合には更新処理が行われ、なければ登録処理が行われます

先ほどModelを作成したときにprimaryKeyを設定したのはここに理由があります。Realmに登録されているデータと一致しているかの判断基準で使われるのでprimaryKeyは設定しましょう!

また、読み取りと組み合わせて下記のように取得したデータのプロパティの更新も可能です

val realmFilterNameData = realm.objects(User.self).where { $0.name = "テスト"}

for data in realmFilterNameData {
    try? realm.write {
        data.name = "テスト更新"
    }
}
削除

削除はrealm.deleteでできます

let user = User()
user.id = 1
user.name = "テスト"
user.age = 2


try? realm.write {
    realm.delete(user)
}

もしくは検索結果から削除することも可能です

try? realm.write {
    realm.delete(realm.delete(User.self))
}

基本的な操作はこれだけです。学習コストが低いのがわかると思います

あとは少し補足をしようかと思います

Realmのデータの確認方法

実際のRealmのデータがどうなっているか確認したい時があると思います。先ほど大まかにRealmは端末に保存されるDBと記載しましたが、Realmデータはファイルとして端末に保存されています

実機もしくはシミュレーターで対象のアプリを起動します

XcodeのWindow→Devices and Simulatorsを選択します。対象のアプリを選択して歯車マークを選択して「Download Container」を選択します

f:id:kumaskun:20220206005750p:plain

ダウンロードしたファイルを右クリックで「パッケージの内容を表示」を選択してAppData→Documents配下に「default.realm」が存在していると思います

「default.realm」がRealmのデータになるので、Realm Studioなどでファイルを開くとデータを確認できます

初期データを追加する

Realmファイルを追加して初期データとして扱うことができます

下記のように起動時にRealmファイルが存在しない場合には初期データをコピーしてrealmファイルとするようにできます

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

  // アプリで使用するdefault.realmのパスを取得
  let defaultRealmPath = Realm.Configuration.defaultConfiguration.fileURL!

  // 初期データが入ったRealmファイルのパスを取得
  let bundleRealmPath = Bundle.main.url(forResource: 「ファイル名を書く」, withExtension: "realm"(拡張子))      

  // アプリで使用するRealmファイルが存在しない(= 初回利用)場合は、初期データをコピーする
  if !FileManager.default.fileExists(atPath: defaultRealmPath.path) {
    do {
      try FileManager.default.copyItem(at: bundleRealmPath!, to: defaultRealmPath)
    } catch let error {
      }
  }        

  return true
}    

マイグレーションについて

テーブルを追加する場合には自動的にマイグレーションされますが、変数名を変更したり、カラムを削除したりする場合には不整合のエラーが発生します

ローカルで開発している場合にはアプリを削除して新規でアプリを入れ直せばいいのですが、リリースしているアプリではそうはいきません

リリース済みのアプリではマイグレーションが必要になります

マイグレーションの処理は以下のようになります

Realm.Configuration.defaultConfiguration = Realm.Configuration(
        schemaVersion: 1,
        migrationBlock: { migration, oldSchemaVersion in
            if(oldSchemaVersion < 1) {
                // スキーマ名をnameからfullNameに変更
                migration.renameProperty(onType: User.className(), from: "name", to: "fullName") 
    // ageをStringに変更
                migration.emurateObjects(ofType: User.className()) { oldObject, newObject in
                    if let age = oldObject["age"] as? Int {
                        newObject?["age"] = String(age)
                    }
                }
            }
       }
    })

schemaVersionスキーマのバージョンを指定します。最初は0になります

今回はスキーマのバージョンを1にアップした状態で起動時にスキーマのバージョンが0の人はマイグレーションの処理が行われます(oldSchemaVersionがアプリ起動時のスキーマバージョンになります)

deleteRealmMigrationNeededについて

Realmの初期化でRealmの初期化(起動)について書きましたが、もしRealmの初期化や起動に失敗した場合はエラーが発生し続けます(let realm = try! Realm()でforce unwrapしていると失敗する可能性がある)

そこで失敗した場合にはdeleteRealmMigrationNeededという失敗した場合には初期化するという設定値があります

そこで下記のように失敗した場合には初期化することでクラッシュを防ぐことができます

また、Realmを起動する前にデータを更新したり取得しようとするとエラーが発生するので、realmにlazyをつけるなので解消する方法があります

do {
    let realm = try Realm()
} catch let error {
    var config = Realm.Configuration.defaultConfiguration
    config.deleteRealmMigrationNeeded = true
    try? Realm(configuration: config)
}

最後に

基本的にドキュメントを確認すると基本的な動作の実装については書かれています

下記参照にリンクを記載しているので確認してみてください

次回はAndroid編です

参照

docs.mongodb.com

kita-note.com

naoya-ono.com