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 = "テスト"}
上記のrealmFilterNameData
はResult
型のため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」を選択します
ダウンロードしたファイルを右クリックで「パッケージの内容を表示」を選択して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編です