LoginSignup
148
140

More than 5 years have passed since last update.

Realmは「CoreDataもよくわかってないのに新しいDBなんて・・・」という人にこそオススメ

Last updated at Posted at 2015-08-11

つい最近、初めて Realm を導入してみたのですが、非常に簡単に使えました。標題のように、僕は Realm が話題になりはじめた当初、「Core Data もよくわかってないのに新しい DB なんて・・・」 と思っていたのですが、むしろそういう人にこそオススメです。

Core Data はちょろっと使うだけでも Managed とか Context とか Persistent とか Coordinator とか小難しい用語がたくさん出てきますが、Realm は NSUserDefaults のような気軽さ で使うことができます。

たとえばモデルオブジェクトの定義は、

objc
@interface Dog : RLMObject

@property NSString *name;
@property NSInteger age;

@end
swift
class Dog: Object {
  dynamic var name = ""
  dynamic var age = 0
}

と、普段 NSObject を継承しているところを RLMObject 1 に変えるだけ

データの保存は、

objc
RLMRealm *realm = [RLMRealm defaultRealm];

[realm transactionWithBlock:^{
    [realm addObject:mydog];
}]
swift
let realm = try! Realm()

try! realm.write {
  realm.add(myDog)
}

と、addObject: 2 メソッドをトランザクション内で実行するだけ。

データの読み出しは、

objc
Dog *theDog = [[Dog objectsWhere:@"age == 1"] firstObject];
swift
let theDog = realm.objects(Dog.self).filter("age == 1").first

こんな感じです。(※いずれも Realm トップページに載っているサンプルより)

defaultRealm で RLMRealm オブジェクトをさくっと取得できるようにしてくれているところとか、まさに NSUserDefaults 並に簡単に使えるようにしよう、という気遣いを感じます。

NSUserDefaults の代替にも?

NSUserDefaults は設定値のようにデータの数がある程度決まっている場合に便利なものの、上記の Dog のような自前クラスを(そのままでは)まるっと保存できない(しかしそういうケースは多い)、そして Realm はご覧のとおり非常に手軽に自前モデルクラスを保存できてしまうので、Core Data の代替としてだけではなく、ときには NSUserDefaults の代替としても便利かもしれません。

とはいえ押さえておくべきポイント2つ

上記だけでとりあえず試すことはできます。で、僕はドキュメントを読むこともすっとばし、さっそく開発中のアプリ(つまり、ある程度の規模の既存コードがある)で「APIから取得したユーザーデータをキャッシュする」という用途に使ってみようとしました。このモデルクラスを仮に User クラスとします。

objc
@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で代用するようコードを修正します。

objc
@interface User : RLMObject

@property (nonatomic, assign) NSInteger identifier;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *imageUrlStr;

@end

これで動くようになり、Realm ブラウザ で見るとちゃんとデータも保存されるようになります。

または、先の例外メッセージにもあったとおり、ignoredPropertyNames: を使うという手もあります。

objc
+ (nullable NSArray *)ignoredProperties {
    return @[@"imageUrl"];
}

これで imageUrl を永続化対象として無視してくれるようになり、無効な型であっても例外は発生しなくなります。(もちろん Realm への保存もされなくなる)

ポイント2: RLMObject は、取得したスレッドでしか利用できない

Core Data のときは、メインスレッドを妨害しないようにマルチスレッドでそれぞれcontextを用意して云々、というところに気を揉むのがなかなか大変でしたが、Realm の場合、読み出しも保存もどのスレッドからでも行うことができます

objc
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%知っていることなので、今更書くことじゃないかな、、とも思いましたが、僕のように新しいものを使うのに腰が重い人が腰を上げるきっかけに少しでもなれば幸いです。


  1. Swift の場合は Object 

  2. Swift の場合は add 

148
140
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
148
140