SwiftでRealm Databaseを使ってみた


[Realm](https://realm.io/)というものを随分前から聞いたことがありましたが，使ったことがなかったので勉強しました．
Realmの説明とSwiftでRealm Databaseを使ったサンプルをメモします．

## Realmとは

「レルム」と読むそうです．
モバイルアプリのデータ保存やデバイス間データ同期をサポートしてくれるもので，3つの製品ラインナップがあります．

### Realm Database

* SQLiteやCore Dataの代替になるデータストアライブラリ
* 完全無料で利用できる
* ORMやSQLiteより早いらしい
* 次のようなメリットがある
    * 検索が早い
    * スレッドセーフ
    * クロスプラットフォーム
    * 暗号化
    * リアクティブ（データの変更がUIに同期できる）

### Realm Platform

* Realm Databaseで管理するデータをクラウドでマルチデバイスに同期できるmBaaSサービス
* Realm Database自体がオフラインファーストな作りになっているので，通信が不安定な場合などにもデータの整合性やユーザビリティを担保できる
* データベースだけでなくユーザアカウントの管理やFaaSやAPIブリッジ（他システム連携）なども用意されている
* 使える機能やサポートのレベル別に[3つのサービス](https://realm.io/jp/pricing)が用意されている（Developer Editionは無料）
    * Developer Edition
    * Professional Edition
    * Enterprise Edition

### Realm Studio

* Realm Platform上のデータを参照したり操作したりできる開発ツール（アプリ）

## 今回試してみたこと

iOSでプラットフォームに依存しないデータストアを試してみたかったので，Realm DatabaseをSwiftで使ってみました．
Realm Platformを使ったクラウドへの保存やデバイス間データ同期は試していません．

サンプルアプリとして，簡単なTodo管理アプリを実装し，Todoの追加・削除・永続化を行ってみました．

アプリの動作は次のような感じです．
![screenshot.gif](https://qiita-image-store.s3.amazonaws.com/0/35592/27c8ff8f-1a6b-a713-75a1-220e1af709f0.gif "screenshot.gif")

ソースは以下のGitHubリポジトリに置いています．

[ottijp/RealmFirstTry](https://github.com/ottijp/RealmFirstTry)

### 環境

* Xcode 9.2
* Swift 4.0
* iOS 11.2 (simulator)
* RealmSwift 3.0.2

### Realmライブラリのインストール

[CocoaPods](https://cocoapods.org/)でインストールしました．

`pod init`し，Podfileに`pod 'RealmSwift'`を追記した後で，次のコマンドを実行します．

```shell:
pod repo update
pod install
```

`pod repo update`を実行しないと古いバージョンがインストールされる場合があるので注意が必要です（私の環境では，実行しない場合は2.7.0がインストールされましたが，実行したあとは3.0.2がインストールされました．）

### モデルの作成

モデルはクラスとして実装しますが，次の点に注意です．

* モデルは`Object`（RealmSwiftのクラス）を継承する必要がある
* 1:nのモデルはSwiftのArrayではなく，`List`（RealmSwiftのクラス）を使う必要がある（今回のサンプルでは1:nモデルは作っていません）
* モデルの定義を変えた後は[Migration](https://realm.io/docs/swift/latest#migrations)をしないとアプリがクラッシュしました

Todoのアイテムモデルを表す次のようなクラスを作りました．

```swift:TodoItem.swift
class TodoItem: Object {
  @objc dynamic var title = ""
}
```

### Realmへのデータ追加

新規Todoの追加処理は次のように実装しました．

```swift:TodoListViewController.swift
private var realm: Realm!

func addTodoItem(title: String) {
  try! realm.write {
    realm.add(TodoItem(value: ["title": title]))
  }
}
```

### Realmからデータ削除

既存Todoの削除処理は次のように実装しました．

```swift:TodoListViewController.swift
private var realm: Realm!
private var todoList: Results<TodoItem>!

func deleteTodoItem(at index: Int) {
  try! realm.write {
    realm.delete(todoList[index])
  }
}
```

### Realmデータの変更通知

[Realm notification](https://realm.io/docs/swift/latest#notifications)という仕組みで変更通知を受けることができます．
本当はUITableViewのセル挿入やセル除去をきちんとやるべきです（Realmのドキュメントではそうしてます）が，簡略化のために追加削除の場合も一様に`reloadData()`をしちゃってます．

```swift:TodoListViewController.swift
private var realm: Realm!
private var todoList: Results<TodoItem>!
private var token: NotificationToken!
@IBOutlet weak var tableView: UITableView!

override func awakeFromNib() {
  super.awakeFromNib()

  // RealmのTodoリストを取得し，更新を監視
  realm = try! Realm()
  todoList = realm.objects(TodoItem.self)
  token = todoList.observe { [weak self] _ in
    self?.reload()
  }
}

func reload() {
  tableView.reloadData()
}
```

## その他

今回は試していませんが，他にも[条件付きクエリ](https://realm.io/docs/swift/latest#queries)や[モデル間のリレーション定義](https://realm.io/docs/swift/latest#relationships)，[データファイルの暗号化](https://realm.io/docs/swift/latest#encryption)など便利な機能がいっぱいなので，今度試してみたいです．