つい最近、初めて Realm を導入してみたのですが、非常に簡単に使えました。標題のように、僕は Realm が話題になりはじめた当初、「Core Data もよくわかってないのに新しい DB なんて・・・」 と思っていたのですが、むしろそういう人にこそオススメです。
Core Data はちょろっと使うだけでも Managed とか Context とか Persistent とか Coordinator とか小難しい用語がたくさん出てきますが、Realm は NSUserDefaults のような気軽さ で使うことができます。
たとえばモデルオブジェクトの定義は、
@interface Dog : RLMObject
@property NSString *name;
@property NSInteger age;
@end
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
}
と、普段 NSObject を継承しているところを RLMObject 1 に変えるだけ、
データの保存は、
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addObject:mydog];
}]
let realm = try! Realm()
try! realm.write {
realm.add(myDog)
}
と、addObject:
2 メソッドをトランザクション内で実行するだけ。
データの読み出しは、
Dog *theDog = [[Dog objectsWhere:@"age == 1"] firstObject];
let theDog = realm.objects(Dog.self).filter("age == 1").first
こんな感じです。(※いずれも Realm トップページに載っているサンプルより)
defaultRealm
で RLMRealm オブジェクトをさくっと取得できるようにしてくれているところとか、まさに NSUserDefaults 並に簡単に使えるようにしよう、という気遣いを感じます。
###NSUserDefaults の代替にも?
NSUserDefaults は設定値のようにデータの数がある程度決まっている場合に便利なものの、上記の Dog のような自前クラスを(そのままでは)まるっと保存できない(しかしそういうケースは多い)、そして Realm はご覧のとおり非常に手軽に自前モデルクラスを保存できてしまうので、Core Data の代替としてだけではなく、ときには NSUserDefaults の代替としても便利かもしれません。
##とはいえ押さえておくべきポイント2つ
上記だけでとりあえず試すことはできます。で、僕はドキュメントを読むこともすっとばし、さっそく開発中のアプリ(つまり、ある程度の規模の既存コードがある)で「APIから取得したユーザーデータをキャッシュする」という用途に使ってみようとしました。このモデルクラスを仮に User クラスとします。
@interface User : RLMObject
@property (nonatomic, assign) NSUInteger identifier;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSURL *imageUrl;
@end
こんな感じで、NSObject を RLMObject にとりあえず変更し、保存のところをちょろっと書いて、アプリを実行してみると、次のような例外が発生してしまいました。
Can't persist property 'identifier' with incompatible type. Add to ignoredPropertyNames: method to ignore.'
サンプルはうまくいったのになぜダメだったのでしょうか?さっくり使えるのは事実なのですが、最低限、これだけはおさえておく必要がある、と思ったポイントを以下2つだけ紹介します。
###ポイント1: プロパティとして使える型
ドキュメント読めばちゃんと書いてあるのですが、プロパティとして使える型は以下のとおり。
- BOOL, bool
- int, NSInteger, long, long long
- float, double, CGFloat
- NSString
- NSDate(少数点以下は切り捨て)
- NSData
また
一対一や一対多のようなリレーションシップのために RLMArray、RLMObject のプロパティも定義できます。RLMObject のサブクラスも使えます。
とのことです。
つまり、そういうことなので、上に定義した User クラスのプロパティのうち、NSUInteger型の identifier
と、NSURL型の imageUrl
はNGです。それぞれNSIntegerやNSStringで代用するようコードを修正します。
@interface User : RLMObject
@property (nonatomic, assign) NSInteger identifier;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *imageUrlStr;
@end
これで動くようになり、Realm ブラウザ で見るとちゃんとデータも保存されるようになります。
または、先の例外メッセージにもあったとおり、ignoredPropertyNames:
を使うという手もあります。
+ (nullable NSArray *)ignoredProperties {
return @[@"imageUrl"];
}
これで imageUrl
を永続化対象として無視してくれるようになり、無効な型であっても例外は発生しなくなります。(もちろん Realm への保存もされなくなる)
###ポイント2: RLMObject は、取得したスレッドでしか利用できない
Core Data のときは、メインスレッドを妨害しないようにマルチスレッドでそれぞれcontextを用意して云々、というところに気を揉むのがなかなか大変でしたが、Realm の場合、読み出しも保存もどのスレッドからでも行うことができます。
dispatch_async(dispatch_queue_create("background", 0)) {
// 保存したり、読み出したり
}
おお、これは楽でいい!・・・と思うわけですが、その代わり、
All Realm instances are thread-confined
Realm のインスタンス、つまり RLMObject などは、それを取得したスレッドでしか使用できない、という点に注意が必要です。
つまり先ほどの User モデルクラスの例でいうと、メインスレッドでクエリを実行し User インスタンスを取得したとして、画像を非同期で取得するために imageUrlStr
プロパティにバックグラウンドスレッドからアクセスしようとすると例外が発生します。
> Realm accessed from incorrect thread
そんなこんなで、既にある程度の規模になっているプロジェクトのモデルクラスをいきなり RLMObject に変更した場合、結構な確率で上の例外に出くわすんじゃないかと思います。
この件は知ってさえいれば簡単に対処できますが、どう実装するのがベストプラクティスなのか、というあたりは色々議論されてるところだと思うので、これから色々調べて先人の知恵を借りようと思っています。
##まとめ
Realm 簡単なのでオススメです!ということを書きました。紹介した「ポイント」は Realm 使ってる人であれば100%知っていることなので、今更書くことじゃないかな、、とも思いましたが、僕のように新しいものを使うのに腰が重い人が腰を上げるきっかけに少しでもなれば幸いです。