Realm Swiftを使ったiOSアプリを作ったので、コードの一部を実装サンプルとして共有してみる

# おことわり

- 本稿で前提としている開発環境のバージョンは古いです。
- サンプルとして記載してるコードはSwiftらしくありません。
    - 具体的には、安易に継承を使ってしまっていますが、Protocolを活用するほうが好ましいと思われます。

以上を踏まえつつ、お読みください。

# はじめに

## Realmとは

Version 1.0公開時（2016年5月）の公式ニュースより引用
https://realm.io/jp/news/realm-1.0/
>RealmはSQLite上に構築されたいわゆるORMではありません。
>私たちはモバイルアプリケーション開発者のために1からデータベースを開発しています。
>そのため単なるキーバリューストアではなく、データベースエンジンが保持するデータに動的に対応付けられたネイティブなオブジェクトを提供します。
>このことにより、Realmは簡潔なAPIとパフォーマンスの両立を達成しています。
>Realmを用いることで、複雑なデータのモデリング、オブジェクト間のリンク、高度なクエリのすべてが可能となります。

## Realm Swiftの資料

公式ドキュメント
https://realm.io/jp/docs/swift/latest/

とても充実していて、使い方については、これだけ読めば足りる感じ。
インストール手順もバッチリ記載されています。

## 使ってみた感想

私は目下、社内コミュニティで研究目的のiOSアプリを作成しており、そのアプリで使ってみました。

私はバリバリの業務系エンジニアで、iOSアプリは本業ではありません。
今まではOracleをメインにRDBMSしか使ったことがなかったので、オブジェクトのままデータを管理するという概念は目から鱗でした。

Realm Swiftを私なりに一言で表現するならば、「Swiftの、Swiftによる、Swiftのためのデータベース」。
Realm Swiftを利用することでコードが非常に簡潔になります。

「モバイルアプリケーション開発者のために1から」開発されたデータベース、という触れ込み通りと感じました。

# サンプルコード

私はXcode + Swiftは初級者、Realmは初学者なので、イケてない部分もあるかと思います。 :sweat_smile: 
したがって、あくまで「一事例」として捉えていただければと思います。

コメントでのツッコミは歓迎です。

## 環境

|Item|Version|
|:--|:--|
|Xcode|7.3.1|
|Swift|2.2|
|Realm Swift|1.0.2|
|iOS|9.3|

## Realmモデルクラス

今回のアプリで扱うデータ、請求と入金のモデル。
Realmでは、Objectクラスを継承する決まりになっています。

```swift:ReceivableModel.swift
import Foundation
import RealmSwift

/// 請求モデルクラス
class ReceivableModel: Object {
    dynamic var id: String = ""
    dynamic var customerId: String = ""
    dynamic var customerName: String = ""
    dynamic var amount: String = ""
    dynamic var settlementDate: String = ""
    dynamic var note: String = ""
    override static func primaryKey() -> String? {
        return "id"
    }
}
```

```swift:DepositModel.swift
import Foundation
import RealmSwift

/// 入金モデルクラス
class DepositModel: Object {
    dynamic var id: String = ""
    dynamic var sender: String = ""
    dynamic var amount: String = ""
    dynamic var depositDate: String = ""
    dynamic var note: String = ""
    override static func primaryKey() -> String? {
        return "id"
    }
}
```

## Data Access Object（DAO）

商用であれば将来的な拡張を踏まえてFactoryをかませると思いますが、今回は研究用アプリなので、そこまでは実装していません。

### プロトコル

請求と入金のAccessorクラスはこのプロトコルで宣言されたメソッドを実装します。
モデルクラスは実装時に確定したいため、associatedtypeにより別名化しています。
Resultsクラスは、Realmの結果セットを格納するためのクラス（Realmコレクションクラス）です。

```swift:AccessorProtcol.swift
import Foundation
import RealmSwift

/// RealmデータベースへのAccessorはこのProtcolを実装する
protocol AccessorProtcol {
    associatedtype ObjectType: Object
    func getByID(id: String) -> ObjectType?
    func getAll() -> Results<ObjectType>?
    func set(data: Object) -> Bool
    func delete(data: Object) -> Bool
}
```

### Baseクラス

請求と入金のAccessorクラスの継承元です。
Baseを作った意図は以下の通りです。

- モデルの定義変更時は、Realmのインスタンス作成前に[マイグレーション・コードを書く必要がある](https://realm.io/jp/docs/swift/latest/#section-40)ようなので、Realmのインスタンス作成は一箇所に集約した方が良さそう。

- Realmでは、異なるモデルであっても、追加/更新・削除のAPIインターフェイスが同一なので共通化できる。

```swift:AccessorBase.swift
import Foundation
import RealmSwift

/// RealmデータベースへのAccessorはこのclassを継承する。
class AccessorBase {
    
    let realm: Realm
    
    /// コンストラクタ
    init() {
        // Realmオブジェクト生成
       realm = try! Realm()
    }
    
    /// データをUpdateする
    /// - parameter data: データ
    /// - returns: true: 成功
    func set(data: Object) -> Bool {
        do {
            try realm.write {
                realm.add(data, update: true)   //プライマリキーで上書きする
            }
            return true
        } catch {
            print("\n--Error! AccessorBase#set")
        }
        return false
    }
    
    /// データをDeleteする
    /// - parameter data: データ
    /// - returns: true: 成功
    func delete(data: Object) -> Bool {
        do {
            try realm.write {
                realm.delete(data)
            }
            return true
        } catch {
            print("\n--Error! AccessorBase#delete")
        }
        return false
    }

}
```

### 請求のAccessor

```swift:ReceivablesAccessor.swift
import Foundation
import RealmSwift

/// RealmデータベースへのAccessor: ReceivableModel
class ReceivablesAccessor: AccessorBase, AccessorProtcol {

    /// Singleton
    static let sharedInstance = ReceivablesAccessor()
    private override init() {
        super.init()
    }

    /// ID指定でデータを1件取得する
    /// - parameter id: ID
    /// - returns: ReceivableModel
    func getByID(id: String) -> ReceivableModel? {
        let models = super.realm.objects(ReceivableModel).filter("id = '\(id)'")
        if models.count > 0 {
            return models[0]
        } else {
            return nil
        }
    }
    
    /// データを全件取得する
    /// - returns: ReceivableModelのコレクション
    func getAll() -> Results<ReceivableModel>? {
        return super.realm.objects(ReceivableModel).sorted("id")
    }
    
}
```

### 入金のAccessor

```swift:DepositsAccessor.swift
import Foundation
import RealmSwift

/// RealmデータベースへのAccessor: DepositModel
class DepositsAccessor: AccessorBase, AccessorProtcol {
    
    /// Singleton
    static let sharedInstance = DepositsAccessor()
    private override init() {
        super.init()
    }
    
    /// ID指定でデータを1件取得する
    /// - parameter id: ID
    /// - returns: DepositModel
    func getByID(id: String) -> DepositModel? {
        let models = super.realm.objects(DepositModel).filter("id = '\(id)'")
        if models.count > 0 {
            return models[0]
        } else {
            return nil
        }
    }
    
    /// データを全件取得する
    /// - returns: DepositModelのコレクション
    func getAll() -> Results<DepositModel>? {
        return realm.objects(DepositModel).sorted("id")
    }
    
}
```

## DAO呼び出し側

### 書き込み

```swift
        ...
        let modelReceivable = ReceivableModel()
        ... //値のセットを行う
        if !ReceivablesAccessor.sharedInstance.set(modelReceivable) {
            return
        }
        ...
```

### UITableViewに請求データを表示

```swift:ReceivableTableViewController.swift
    ...
    /// セルの数
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        guard let receivables = ReceivablesAccessor.sharedInstance.getAll() else {
            // データなし
            return 0
        }
        return receivables.count
    }
    ...
    /// セルを設定
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        guard let receivables = ReceivablesAccessor.sharedInstance.getAll() else {
            // データなし
            return UITableViewCell()
        }
        // indexのcellを設定
        let receivable = receivables[indexPath.row]
        let cell = tableView.dequeueReusableCellWithIdentifier("ReceivableCell", forIndexPath: indexPath) as! ReceivableTableViewCell
        cell.setData(receivable)
        return cell
    }
    ...
```

```swift:ReceivableTableViewCell.swift
import UIKit

class ReceivableTableViewCell: UITableViewCell {

    @IBOutlet weak var CustomerNameLabel: UILabel!
    @IBOutlet weak var AmountLabel: UILabel!
    @IBOutlet weak var SettlementDateLabel: UILabel!
    @IBOutlet weak var NoteLabel: UILabel!
    
    /// データを画面項目にセットする
    /// - parameter model: データモデル
    func setData(model: ReceivableModel) {
        self.CustomerNameLabel.text = model.customerName
        self.AmountLabel.text = model.amount
        self.SettlementDateLabel.text = model.settlementDate
        self.NoteLabel.text = model.note
    }

}
```