この記事は - スピカ Advent Calendar 2015 - 3日目の記事です。
弊社の サービス では Realm を絶賛使っていますが、その検索条件部分をより Swift っぽく書いてみようをテーマに投稿します。
はじめに
Realm では通常、以下の filter
メソッドに文字列で条件を記述するのですが、タイポなどした場合は実行時に、はじめてエラーとして現れます。
public func filter(predicateFormat: String, _ args: AnyObject...) -> Results<T>
これでは折角のコンパイル言語としての Swift の魅力が半減してしまいます。
そこで今回は QueryKit というライブラリを組み合わせて、そういった部分を解消しようという試みになります。
QueryKit
QueryKit 自体は CoreData 向けのライブラリなのですが、CoreData, Realm どちらも NSPredicate
を利用したインターフェースになっているため、 Realm でも利用することが出来ます。
QueryKit を用いると NSPredicate
が以下のような構文で表現されます。
// Name is equal to Kyle
name == "Kyle"
>
// Name is either equal to Kyle or Katie
name << ["Kyle", "Katie"]
>
// Age is equal to 27
age == 27
>
// Age is more than or equal to 25
age >= 25
>
// Age is within the range 22 to 30.
age << (22...30)
具体例
定義部分
以下のように RealmSwift.Object
を普通に継承したクラスに対して、必要に応じて Arrtibute< AttributeType>
型のクラス変数を定義します。
import RealmSwift
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
}
class Person: Object {
dynamic var name = ""
}
import QueryKit
extension Dog {
static var name: Attribute<String> { return Attribute("name") }
static var age: Attribute<Int> { return Attribute("age") }
}
extension Person {
static var name: Attribute<String> { return Attribute("name") }
}
クエリ部分
Realm からオブジェクトを取り出す際は、以下のように記述することが出来ます。
let realm = try! Realm()
// "name = %@", "Taro"
realm.objects(Dog).filter(Dog.name == "Taro")
// "name IN %@", ["Taro", "Jiro"]
realm.objects(Dog).filter(Dog.name << ["Taro", "Jiro"])
// "name = %@ AND age > %@", "Taro", 2
realm.objects(Dog).filter(Dog.name == "Taro" && Dog.age > 2)
メリット
- 文字列ではないので、入力補完が働く ✨
- Generics を用いて型の一致も判断されるので、以下のような式はコンパイル時にエラーとなる ✨
realm.objects(Dog).filter(Dog.age > "2")
// Binary operator '>' cannot be applied to operands of type 'Attribute<Int>' and 'String'
おわりに
駆け足でしたが、 Realm に QueryKit を組み合わせた例を紹介してみました 😎