【Swift4】Realm+Codableを使ったお手軽なDB Part.3(クエリ編)


はじめに

前回の記事【Swift4】Realm+Codableを使ったお手軽なDB Part.2(リレーション編)の続き、Part3です。

今回はRealmへのクエリをまとめました!😀
Codableから少し遠ざかってしまいますが、あしからず。

Realmへのクエリ

RealmへのクエリはCore Dataと同様にNSPredicateを使用します。formatを文字列として指定する必要があったりと、あまり使い勝手がよくありません。
そのため、SwiftでRealmを使う時のTips(3) NSPredicate編をそのまま使わせて頂いています😎

テーブル全件取得

realm.objects(<Type>.self)

1件取得

プライマリーキーが設定されているテーブルは、以下のように取得できます。

realm.object(ofType: <Type>.self, forPrimaryKey: <Key>)

クエリを用いての取得(よく使うもの)

// =
realm.objects(<Type>.self).filter(NSPredicate("<key>", equal: "<query>" as AnyObject))
// !=
realm.objects(<Type>.self).filter(NSPredicate("<key>", notEqual: "<query>" as AnyObject))
// 部分一致
realm.objects(<Type>.self).filter(NSPredicate("<key>", contains: "<query>" as AnyObject))
// IN句
realm.objects(<Type>.self).filter(NSPredicate("<key>", valuesIn: [<query>] as [AnyObject]))

// 条件の結合
let predicates = NSPredicate.empty()
    .and(NSPredicate("<key>", equal: "<query>" as AnyObject))
    .or(NSPredicate("<key>", equal: "<query>" as AnyObject))
    ...
realm.objects(<Type>.self).filter(predicates)

// 結果のソート
realm.objects(<Type>.self).sorted(byKeyPath: "<key>", ascending: false)

このように、クラス拡張とメソッドチェインを使うことで、Realmへのアクセスが断然使いやすくなりました!

enum定義

NSPredicateの拡張によって使いやすくなったクエリですが、keyを直打ちするのがスマートではありません🙃
Typoやカラムの変更があった際に、コンパイルしても気づくことが出来ず、実行時にクラッシュする原因になります。

そこで各テーブル(クラス)に各変数の名前をenumで定義してあげます。前回の記事で使用したSalesクラスを元にすると、

enum StringKey: String {
    case id
    case date
    case birthday
    case productID
    case employeeID
    case employee
    case products
    case customer
}

これとクエリを組み合わせると、

realm.objects(Sales.self).filter(NSPredicate(Sales.StringKey.id.rawValue, equal: 553 as AnyObject))

このような感じになります。少し1行が長くなってしまっていますが、コンパイラの支援を受けることが出来ます!

クエリのラップクラス

Clean Architectureなどのアーキテクチャを使用してアプリ開発を行う場合、実際のRealmへのアクセスとデータベースへのI/Fは分離し、データベースに依存しないような構成を取ることがあると思います。

DataStore.swift
class DataStore {
    let realm = try! Realm()

    func readAll<T: Object>() -> [T] {
        let result = realm.objects(T.self)
        return Array(result)
    }

    func readEqual<T: Object>(ofTypes: String, forQuery: AnyObject) -> [T] {
        let result = realm.objects(T.self).filter(NSPredicate(ofTypes, equal: forQuery))
        return Array(result)
    }

    func create<T: Object>(data: [T]) {
        try! realm.write {
                realm.add(data)
            }
    }

    func delete<T: Object>(_ type: T.Type, predicates: NSPredicate) {
        try! realm.write {
                let obj = realm.objects(T.self).filter(predicates)
                realm.delete(obj)
            }
    }
}

そのため、このようなCRUDに相当するクラスを作成しました。(今回は説明のため、force-unwrappingはそのままです)
Genericsメソッドにしてあるので、このクラスを使うことですべてのテーブルにアクセス出来るようになります😎

さいごに

今回の記事では、クエリ編としてRealmをより便利に使うためのポイントを紹介しました。

続編書きました!
【Swift4】Realm+Codableを使ったお手軽なDB Part.4(番外編)


株式会社Nexceed にて、一緒に働いてくれる仲間を募集中です
- 建築業界に革命を!!AI × BIMの世界を作り出すアプリエンジニア募集!