RealmのCRUDを理解しつつ、個人開発アプリを作成してみよう！


## 概要
**Realm**についての概要と他のサービスとの比較に加え、**Realm**を使用した簡易的なiOSアプリケーションの作成方法について、解説します！

#### この記事を読むことで作成できるTODOアプリ
![ezgif-2-e36c12636f.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2630898/3572b63b-15f8-bdbf-8d04-076a0a50cf76.gif)

上記のようなアプリの実装方法を確認したい方は、「実装方法」までスキップしてください！

この記事を読みながら写経すればある程度基礎的なTODOアプリを作成することが可能になるはずなので、ぜひ試してみてください！

面白そうだなと思ったら、この記事を**お気に入り**していただけると嬉しいです！

#### この記事はこんな方におすすめです
- 自力でポートフォリオを作成して、未経験でiOSエンジニア転職を狙う方やSwiftで簡単なアプリを作りたい方
- RealmのCRUDを理解したいエンジニアの方
- RealmとFirebaseのどちらを使用したらいいかわからない方


### その他
:::note info
**この記事で説明すること**
- ソースコードを用いたRealmの基本的な使用方法について
- Realmについての簡単な基本情報の説明
- Realmを用いた簡単なアプリケーション作成方法について
:::

:::note alert
**この記事で説明しないこと**
- UIKit等の他ライブラリの使用方法について(EX:TableViewの実装方法やボタンの実装方法等)
- アーキテクチャ(実装の際の作法的なもの)の詳細な解説
:::


##### 開発環境
**Mac**
macOS Monterey 12.5
MacBook Air(M1, 2020) メモリ8G

**XCode**
Version 14.0 

**iPhone**
iOS 16.1
iPhone13

## 前提条件
まずそもそも、「Realmってなんやねん？」「勉強してなんかいいことあるんか？」とお思いの方、いらっしゃると思いますが

:::note info
**結論**
**iOSエンジニアの方であれば、確実に学んだ方がいい**
:::

では、その「Realm」についてですが、めっちゃくちゃ簡単に特徴を説明すると以下の3点が挙げられます。
- iPhone内にデータを保持する仕組みを無料で作成することができる
- 超高速でデータを取得することができ、インターネット通信がない場所でも、データの「登録・取得・更新・削除」が行える。
- 実装がめちゃくちゃ簡単で、すぐに理解できる。その割に、使い勝手もいい。

以上が大きな理由です。

まず1点目の「iOSアプリの場合iPhone内にデータを保持する仕組みを無料で作成することができる」ですが、もともとiOSを開発する際にデバイス内にデータを保持しておける仕組み**UserDefault**というものがあります。以下で特徴を比較してみました。

###### UserDefault
- プロジェクト作成時からデフォルトで使用できる
- 「String」や「Bool」「Int」「Date」「Data」等のあらかじめ存在する限られた型でしかデータを保存することができない。(User型等をそのまま保存できない)
- データを保存しすぎると、アプリの動きがもっさりしたり、最悪の場合バグが生まれる

###### Realm
- Pod等でライブラリの管理は必要になる
- 任意の型を指定してデータを保存できる
- ある程度データを保存してももっさりすることはない。

おまけ
###### Cloud Firestore(Firebase)
- 個人開発で、クラウドにデータを保存する機能を使用するにはもってこい
- Firebaseの他のサービスである、Authentificationでアカウント管理が容易にできたり、Storageを使用することで画像の保存もできる万能サービス
- 実装コストがRealmよりも高く、すぐにアプリを出したい場合にはあまり向かない

2点目の「超高速でデータを取得することができ、インターネット通信がない場所でも、データの「登録・取得・更新・削除」が行える。」です。通常、データの取得や更新は、サーバーを通してデータの登録等をしますが、Realmはデータを端末に保存するのでオフライン環境下でも全く影響がありません。

3点目の「実装がめちゃくちゃ簡単で、すぐに理解できる。その割に、使い勝手もいい。」ですが、続きの実装方法で解説します！

上記3つは間違いなくメリットなのですが、強いていうならばデメリットが1つあります。それは、
###### 他のユーザーとのデータのやり取りができない

上記のように、**「他ユーザーとデータのやり取りをしたい」** であったり、**そもそも、デバイス内にはデータを持たせたくない**という場合、には**Firebase**というGoogleが提供してくれている機能を使用することを考えてみるのも1つの手です！
ただ、どちらも使用したことがある身からすると、Firebaseは**今回学習するRealmよりも学習コストが高い**ことと**今回作成するTODOアプリのように他ユーザーとのデータのやり取りを行わないアプリを作成**する場合は一旦、**Realm**を使用してみましょう！
(※Firebaseの記事に関しても、今後書きたいと思います。)

## 実装方法
今回作成するサンプルアプリ要件としては、以下の通りです。

##### TodoModel
- [ ] TodoModel型を作成

##### RealmDBConverter
- [ ] Results型から任意の型(今回はTodoModel)に変換できる

##### TodoListViewController
- [ ] 名前をつけてTodoを投稿することができる
- [ ] 投稿時間も保存ができる
- [ ] 投稿したTodoは、即座に画面(TableView)に反映して確認できる
- [ ] 投稿したTodoをタップすると、編集画面に遷移できる
- [ ] 登録したTodoを全て削除することができる
- [ ] 名前で登録済みのTodoを検索できる

![IMG_EB4689E18EA8-1.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2630898/75729f44-397a-9fef-14e3-d00ada25ba39.jpeg)


##### TodoEditlViewController
- [ ] 表示中のTodoのみ削除することができる
- [ ] 表示中のTodoの名前を変更することができる

![IMG_7F3669109722-1.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2630898/d5c4e894-00b6-b290-83a4-1cafbf2d41b0.jpeg)

また今回は、アーキテクチャ等の説明は割愛するためViewControllerにベタばりのソースコードにします。
それぞれ、Xibファイルのコードは共有しませんので各々よしなに作成してください。

```TodoModel.Swift
import Foundation
import RealmSwift

final class TodoModel: Object {
    // ポイント①
    // TodoModelで保存したい任意の型を作成しています。
    // 今回であれば、保存しておきたい「title」と「regDate」をTodoの値として作成していきます。
    // Realmの使用仕様上、@objc dynamicというのを変数につけてあげないといけないみたいなので、まじない程度に考えてください。
    @objc dynamic var title: String?
    @objc dynamic var regDate: Date?
}

```


おまけファイル
```RealmDBConverter.Swift

import Foundation
import RealmSwift

struct RealmDBConverter {
    
    /// RealmのResults型で取得することになっているデータを任意の型に形成する処理
    /// - Parameter object: RealmのResults型で返却される値
    /// - Returns: RealmのResults型から形成されたされた任意の型の配列
    static func convertToModelFrom<T>(object: Results<T>) -> [T] {
        var list: [T] = []
        for i in 0..<object.count {
            list.insert(object[i], at: 0)
        }
        return list
    }
    // 参考
    // データベースから値取得の際には、以下のような型で結果が返却されます
    // この型を扱いやすいようにコンバートするのが上記の関数です。
    Results<TodoModel> <0z188f01fd0> (
	[0] User {
		title = サウナに行く;
        regDate = 20230401;
	}
}
```

```TodoListViewController.Swift
import UIKit
import RealmSwift

final class TodoListViewController: UIViewController {

    // TODOを表示するデーブルビュー
    @IBOutlet private weak var todoListTableView: UITableView! {
        didSet {
            todoListTableView.delegate = self
            todoListTableView.dataSource = self
            // CustomCellをテーブルビューに紐付けてください
        }
    }
    // インプットバー
    @IBOutlet private weak var inputTodoTextField: UITextField! 

    // MARK:@IBAction
    // Todoをデータベースに登録する関数(画面上では、青ボタン🟦)
    @IBAction private func tappedRegisterTodoButton() {
        let inputText = inputTodoTextField.text ?? ""
        // 登録時間を保存するためのインスタンス
        let currentTime = Date()
        // ポイント②
        // データベースに値を保存する際は、任意の型をインスタンス化してあげて、
        // 保存する値を代入します。
        let todoModel: TodoModel = TodoModel()
        todoModel.title = inputText
        todoModel.regDate = currentTime
        
        // Realm()をインスタンス化してあげて、realmDb.add()でデータベースに値を保存してあげます
        let realmDb = try! Realm()
        try! realmDb.write({
            realmDb.add(todoModel)
            // これが実際に完了したら、以下の関数でデータを取得します。
            self.getTodoListFromRealmDBForPresentingTableView()
        })
    }

    // Todoをデータベースから全件削除する関数(画面上では、赤ボタン🟥)
    @IBAction private func tappedDeleteAllTodosButton() {
        // ポイント③
        let realmDb = try! Realm()
        // 今回は、保存した値を問答無用で全て削除するので、
        // 削除する値を保存した型(今回は、TodoModel)を以下のように削除します
        try! realmDb.write{
            let todoModelTable = realmDb.objects(TodoModel.self)
            realmDb.delete(todoModelTable)
            self.getTodoListFromRealmDBForPresentingTableView()
        }
    }

    // タイトルが一致するTodoをデータベースから検索する関数(画面上では、緑ボタン🟩)
    @IBAction private func tappedSearchTodosButton() {
        let inputText = inputTodoTextField.text ?? ""
        // ポイント④
        // ポイント⑤と同じような要領です。
        var todosList: Results<TodoModel>
        let realmDb = try! Realm()
        // 「Todoのタイトルが入力された文字と一致する」という条件をfilterを用いで実装しています。
        todosList = realmDb.objects(TodoModel.self).filter("title == '\(inputText)'")
        self.todosList = RealmDBConverter.convertToModelFrom(object: todosList)
        todoListTableView.reloadData()
    }

    var todosList: [TodoModel] = []
    
    private func getTodoListFromRealmDBForPresentingTableView() {
        // Todoをデータベースから取得する関数
        // ポイント⑤
        // 取得したい型を指定してあげて、Realmデータベースから値を取得しています。
        var todosList: Results<TodoModel>
        let realmDb = try! Realm()
        todosList = realmDb.objects(TodoModel.self)
        // データを取得してきたら、画面に表示してあげます。
        self.todosList = RealmDBConverter.convertToModelFrom(object: todosList)
        todoListTableView.reloadData()
    }
}
```

```TodoEditlViewController.Swift
import UIKit
import RealmSwift

final class TodoEditlViewController: UIViewController {

    // MARK: - Proparty
    
    private let todo: TodoModel

    // MARK: - Initialize
    
    init(todo: TodoModel) {
        self.todo = todo
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK:@IBOutlet
    // インプットバー
    @IBOutlet private weak var inputTodoTextField: UITextField! 
    
    // MARK:@IBAction
    // 登録したTodoの名前を変更する関数
    @IBAction private func tappedEditTodosButton() {
        let inputText = inputTodoTextField.text ?? ""
        let currentTime = Date()
        let realmDb = try! Realm()
        // ポイント⑥
        // 登録時とほとんど同じです。
        let todoModel = realmDb.objects(TodoModel.self).first!
        try! realmDb.write{
            todoModel.title = inputText
            todoModel.regDate = currentTime
        }
    }

    // 登録した特定のTodoを削除する関数
    @IBAction private func tappedDeleteTodoButton() {
        let realmDb = try! Realm()
        // ポイント⑦
        // 保存したタイトルの名前と一致しているものをRealmデータベース上で検索し、
        // タイトルが一致するもののみを削除する処理にしています。(本来ならば、idなどを保存させるべき...)
        let todoTable = realmDb.objects(TodoModel.self)
        let result = todoTable.where({ $0.title == title }).first!
        try! realmDb.write{
            realmDb.delete(result)
        }
    }
}
```

## さいごに
長くなりましたが、Realmについての解説はここまでです！
僕自身もFirebaseや自社のサーバー関連を扱うようになった後にRealmを触ったので、Realmはめちゃくちゃ簡単に実装できると感じました！
初学者の方や個人開発でアプリをささっと作りたい方には、もってこいなのでぜひ活用していただきたいです！

おまけのファイルに「20230401サウナに行く」と書いていますが、本当にサウナ行ってきます。

## 参考
**Realm公式リファレンス**

https://www.mongodb.com/docs/realm-legacy/jp/docs/swift/latest.html

**Firebase公式リファレンス**

https://firebase.google.com/?hl=ja