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

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

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

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

```objc:objc
@interface Dog : RLMObject

@property NSString *name;
@property NSInteger age;

@end
```

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

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

[^1]: Swift の場合は Object

データの保存は、

```objc:objc
RLMRealm *realm = [RLMRealm defaultRealm];

[realm transactionWithBlock:^{
    [realm addObject:mydog];
}]
```

```swift:swift
let realm = try! Realm()

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

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

[^2]: Swift の場合は `add`

データの読み出しは、

```objc:objc
Dog *theDog = [[Dog objectsWhere:@"age == 1"] firstObject];
```

```swift: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: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.'

サンプルはうまくいったのになぜダメだったのでしょうか？さっくり使えるのは事実なのですが、最低限、これだけはおさえておく必要がある、と思ったポイントを以下２つだけ紹介します。

###ポイント1: プロパティとして使える型

[ドキュメント](https://realm.io/jp/docs/objc/latest/)読めばちゃんと書いてあるのですが、プロパティとして使える型は以下のとおり。

- BOOL, bool
- int, NSInteger, long, long long
- float, double, CGFloat
- NSString
- NSDate(少数点以下は切り捨て)
- NSData

また

> 一対一や一対多のようなリレーションシップのために RLMArray<Object>、RLMObject のプロパティも定義できます。RLMObject のサブクラスも使えます。

とのことです。

つまり、そういうことなので、上に定義した User クラスのプロパティのうち、NSUInteger型の `identifier` と、NSURL型の `imageUrl` はNGです。それぞれNSIntegerやNSStringで代用するようコードを修正します。

```objc:objc
@interface User : RLMObject

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

@end
```

これで動くようになり、[Realm ブラウザ](https://itunes.apple.com/app/realm-browser/id1007457278) で見るとちゃんとデータも保存されるようになります。

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

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

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


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

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

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