iOS
Swift
RealmSwift

RealmSwiftを使う時よく実装するメソッド

私がRealmSwiftを使う時

実装の方法は様々だと思いますが、私はRealmSwiftを使う時には
インタフェースとなるクラスを1つ作成し、永続化や読み込みはこのクラスで行うようにしています。

いままで

Entityごとに保存、読み込みなどの機能を持つメソッドを作成していましたが、
永続化するEntityの種類が増えるとその分だけ同じ機能を持ちながらEntityの種類が違うだけのメソッドを作ってしまっていました。

import RealmSwift

class RealmStoreManager {

    // MARK: Entityの追加

    // EntityAを永続化する
    static func addEntityA(object: EntityA) {
        let realm = try! Realm()
        try! realm.write {
            realm.add(object)
        }
    }

    // EntityBを永続化する
    static func addEntityB(object: EntityB) {
        let realm = try! Realm()
        try! realm.write {
            realm.add(object)
        }
    }

    // MARK: Entityを返す

    // EntityAの配列を返す
    static func entityAList() -> Results<EntityA> {
        let realm = try! Realm()
        return realm.objects(EntityA.self)
    }

    // EntityBの配列を返す
    static func entityBList() -> Results<EntityB> {
        let realm = try! Realm()
        return realm.objects(EntityB.self)
    }
    // ...以下割愛
}

ジェネリクスを用いた実装

そういえばジェネリクスを普段あまり使わないなと思い練習がてら実装してみました。
コード量も減り、より使いやすいインタフェースになりました。

import RealmSwift

class RealmStoreManager {

    // MARK: ジェネリック関数

    /// RealmのObjectEntityを保存
    ///
    /// - Parameter object: Objectを継承したEntity
    static func addEntity<T: Object>(object: T) {
        let realm = try! Realm()
        try! realm.write {
            realm.add(object)
        }
    }


    /// 指定したtypeのEntityListを返却する
    ///
    /// - Parameter type: Entityの型を指定する
    /// - Returns: 指定されたEntityListを返却する
    static func entityList<T: Object>(type: T.Type) -> Results<T> {
        let realm = try! Realm()
        return realm.objects(type.self)
    }


    /// 指定したtypeのEntityListの長さを返す
    ///
    /// - Parameter type: Entityの型を指定する
    /// - Returns: 指定されたEntityListの長さを返す
    static func countEntity<T: Object>(type: T.Type) -> Int {
       return entityList(type: type).count
    }

    /// 指定した型のEntityから任意のプロパティを指定してフィルタリングした配列を返却する
    ///
    /// - Parameters:
    ///   - type: Entityの型を指定する
    ///   - property: フィルタリングをしたいプロパティ名を指定する
    ///   - filter: フィルタリング条件を入力する(型がわからないのでAny型としている)
    /// - Returns: フィルタリングされたEntityが返却される
    static func filterEntityList<T: Object>(type: T.Type, property: String, filter: Any) -> Results<T> {
        return entityList(type: type).filter("%K == %@", property, String(describing: filter))
    }

}

つまづいたこと

上記にある filterEntityList メソッドですが最初はうまく実装できずに実行時エラーとなっていました。

任意のEntityでしかも任意のプロパティをfilterできる関数があれば便利なのにと考えていました。

以下がエラーとなるコード

    /// 指定した型のEntityから任意のプロパティを指定してフィルタリングした配列を返却する
    ///
    /// - Parameters:
    ///   - type: Entityの型を指定する
    ///   - property: フィルタリングをしたいプロパティ名を指定する
    ///   - filter: フィルタリング条件を入力する(型がわからないのでAny型としている)
    /// - Returns: フィルタリングされたEntityが返却される
    static func filterEntityList<T: Object>(type: T.Type, property: String, filter: Any) -> Results<T> {
        return entityList(type: type).filter("%@ == %@", property, String(describing: filter))
    }


どこがダメだったかというと %@ です。
プレースホルダーにはプロパティを置き換える %K を使用することで想定した動作となりました。

参考
RealmSwiftで'Invalid predicate expressions'という例外について:
https://ja.stackoverflow.com/questions/33233/realmswift%E3%81%A7invalid-predicate-expressions%E3%81%A8%E3%81%84%E3%81%86%E4%BE%8B%E5%A4%96%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6

【Swift】Core Dataの使い方。フェッチリクエストで取得するデータを絞り込む:

http://hajihaji-lemon.com/smartphone/swift/coredata-nspredicate/