はじめに
公式ドキュメントに沿った実装をすると、試用するには良いけれども実務上使い勝手が良くないというパターンが出てきます(やや偏見)。というわけで、Realmを使う上での使いやすいアプローチを考えてみます。
RealmのConfiguringで設定できるものを調べたときにふわっと出してみたアプローチはこんな感じでした。
- 面倒臭くても
do-try-catch
を使う - configureを別途設定しておいて、Realmのインスタンスを生成するときに渡してあげる
- マイグレーションは別クラスに切り出ししてしまう。
口先だけの人には極力なりたくないので、実際に手を動かして自分なりのアプローチを作ってみます。自分で自分の首をギュッと締め上げるスタイルでお送りします。
方針
do-try-catch
を導入する- AppDelegateではRealmの初期化処理を行わない
- AppDelegateではRealmのインスタンスだけを持たせる
- Realmの初期化を行うクラスを別途作る
- マイグレーションの処理はRealmの初期化を行うクラスにまとめる
公式ドキュメントではRealmの初期化処理はAppDelegate
で行っています。
ですが、できればAppDelegate
はスッキリさせておきたい。ということで、Realmの初期化処理のほぼ全部を切り出します。
AppDelegateから諸々を排除する
AppDelegateで行っていたRealmの初期化処理などを外に出したり、随時参照できるようにRealmのための変数を作ったりしました。Realmの初期化を行うクラスを作っていないのでエラーが出ますが後で作るので構いません。むしろ作るまでXCode先生に叱られるので、三歩歩けば忘れるほどの頭しかない私にとっては好都合です。
どんどん叱って。
class AppDelegate: UIResponder, UIApplicationDelegate {
var realm:Realm? = nil
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
do {
self.realm = try Realm(configuration:RealmConfiguration().config())
} catch let error as NSError {
print(error)
}
return true
}
せっかくdo-try-catch
を入れたのにエラー処理がお粗末です。実務上は何らかの処理が必要です。この時点でのエラーは主にデータベースファイルを開けないケースだと思うので、例えばまっさらのデータベースファイルを作って処理を続けたり、アプリ自体を再起動してもう一度アクセスを試してみたり、ひとまずユーザに一言伝えたりしてみると親切かもしれません。
方針の先頭3つはクリアしました。
排除されたRealm初期化処理を別クラスに入れる
残りの方針2つを考えていきます。RealmConfiguration().config()
の部分ですね。
class RealmConfiguration: NSObject {
let fileName = データベースファイル名
let version:UInt64 = 今のスキーマバージョン
override init() {
}
func config() -> Realm.Configuration {
var conf = Realm.Configuration()
// この部分にConfigurationの設定を入れていく
// 設定可能な項目一式のうちよく使うであろうものを並べました
conf.fileURL = Bundle.main.url(forResource: self.fileName, withExtension: "realm")
conf.readOnly = false
// この下の2つは順序がシビアかもしれない
conf.schemaVersion = self.version
conf.migrationBlock = self.migration()
return conf
}
func migration() -> MigrationBlock {
return { migration, oldSchemaVersion in
// 最初はゼロからスタート
// 現在のバージョンが3のときの状態
// バージョンアップをすっ飛ばしたとき(0から2など)のときのためにelseを付けない
if (oldSchemaVersion < 1) { self.migration0to1(migration) }
if (oldSchemaVersion < 2) { self.migration1to2(migration) }
if (oldSchemaVersion < 3) { self.migration2to3(migration) }
}
}
func migration0to1(_ migration:Migration) {
// バージョン0から1への処理
}
func migration1to2(_ migration:Migration) {
// バージョン1から2への処理
}
func migration2to3(_ migration:Migration) {
// バージョン2から3への処理
}
・・・続けていく
}
改善の余地だらけですが、これでRealm初期化関連の処理を切り出しできました。データモデルは別途になりますが、スキーマの変更はここを集中的に見ておけば良いです。
改善の余地といえば、private
を付けてよそから呼び出せなくしておくのも必要ですかね。それに、親クラスを作って継承し、本当に要る部分だけを書くようにすればもっと見やすいかもです。それにそれに、MigrationBlock
の中をスキーマ変更の都度追記するのではなく、ループで回せるようにしたいですね。
バージョンはUInt64
ですし、1つずつインクリメントするごとに処理がありますし。
まとめ
これでRealmの初期化処理を1か所にまとめられたので、なにか修正があればそのクラスにだけ集中して対応すれば良くなりました。若干スマートでない部分もありますが。
今更ですが、AppDelegateにRealmのインスタンスを持たせなくてもいいことに気が付きました。データベースへのアクセスを取り仕切るクラスでRealmの初期化を行い、シングルトンで取り回せば良いだけですね。
もしくはデータベースへのアクセスの都度、インスタンスを作るのも良いかもしれません。アクセス頻度が少ないときは有効かもしれません。ちなみにこの方法はインメモリの場合では上手くいかないだろうなとは思います。