SwiftUIでRealmを使えるようになるまでの道のり

SwiftUI初心者がRealmをなんとなく使えるようになるまでの道のりを簡単にメモしておきます。

# 悩む日々
SwiftUI + Realmで検索するとたくさん記事が出てきます。
いろいろ試したのですが、唯一、とりあえず動いた方法を残しておきます。
最終的に参考にしたのは、以下のサイトです。
- [SwiftUI 2.0 Realm DB CRUD Operations - Alternative For Core Data - SwiftUI Tutorials (YouTube)](https://www.youtube.com/watch?v=lx6C6pcL-0M&feature=emb_logo)
- [SwiftUIでいこう！ - RealmをSPMで使う。](https://note.com/dngri/n/na87140a78f2f)
- 公式サイトなど

# 事前準備
まずはXcodeでRealmを使える状態にしてください。
方法はなんでも良い（と思います）。
私はSPMで準備しましたが、これまで通りCocoaPodsでもいいと思います。

# DBの準備
まずは、データを扱うクラスと、Realmの操作を行うクラスを作ってやります。
ちなみに、今回はアプリのログを保存する、というイメージです。
扱うログデータの中身は
- id: Int
- timestamp: Double
- data: String
と仮定します。

```swift:logdb.swft
import Foundation
import RealmSwift

// ログデータ用のクラス
// プライマリキーとしてidを利用
class Log: Object, Identifiable{
    @objc dynamic var id = 0
    @objc dynamic var timestamp = 0.0
    @objc dynamic var data = ""
    
    override static func primaryKey() -> String? {
        "id"
    }
}

//Realmの操作を実際に行うクラス
//ObservableObjetにして、logsの値をContentViewから監視できるようにする
class LogViewModel: ObservableObject{

    @Published var logs: [Log] = []
    
    init(){
        fetchData()
    }

    //データベースの中身を取り出す
    //全てのデータを取り出して、Log型の配列logsに保存
    //Realmに対する例外操作をguardを使って処理する（無視する）パターン
    func fetchData(){
        guard let dbRef = try? Realm() else {
            return
        }
        let results = dbRef.objects(Log.self)
        self.logs = results.compactMap({(log) -> Log? in
            return log
        })
    }

    //データベースにデータを追加する
    //Realmに対する操作を do try catch で例外処理するパターン
    //guardを使うパターンとどちらにするかは例外処理を具体的にどうするかで決めれば良いと思います
    func addData(addlog: Log){
        let log = addlog

        do{
            let dbRef = try Realm()
            try dbRef.write{
                dbRef.add(log)
            }
        }catch let error{
            //例外処理
        }
    }
    
    //データを全て削除する
    func deleteAll(){
        do{
            let dbRef = try Realm()
            try dbRef.write{
                dbRef.deleteAll()
            }
        }catch let error{
            //例外処理
        }
    }
    
}
```

# ContentViewでの処理
ContentView側では、@StateObjectを使ってLogViewModelを利用します。
これでLogViewModelのlogsをContentViewから操作することができるようになります。
例えば、ボタンを押すとその時の時刻からidを生成してデータを追加するようにします。
- 追加ボタン：現在時刻からidを生成してデータを追加
- 確認ボタン：データベース内のデータの一覧をprint
- 削除ボタン：データベース内のデータを全て削除

```swift:ContentView.swift
import SwiftUI
import RealmSwift

struct ContentView: View {
    
    @StateObject var logData: LogViewModel = LogViewModel()

    var body: some View {
        VStack {
            HStack{
                Button(action: {
                    let tmplog: Log = Log()
                    tmplog.id = Int(Date().timeIntervalSince1970 * 1000)
                    logData.addData(addlog: tmplog)
                }, label: {
                    Text("追加")
                        .frame(width: 100, height: 30)
                        .foregroundColor(.black)
                        .background(Color.orange)
                        .cornerRadius(15, antialiased: true)
                })
                .frame(maxWidth: .infinity, alignment: .leading)
                .padding(.trailing, 10.0)
  
                Button(action: {
                    logData.fetchData()
                    print("current logs: \(logData.logs)")  
                }, label: {
                    Text("確認")
                        .frame(width: 100, height: 30)
                        .foregroundColor(.black)
                        .background(Color.orange)
                        .cornerRadius(15, antialiased: true)
                })
                .frame(maxWidth: .infinity, alignment: .leading)
                .padding(.trailing, 10.0)
  
                Button(action: {
                    logData.deleteAll()
                }, label: {
                    Text("削除")
                        .frame(width: 100, height: 30)
                        .foregroundColor(.black)
                        .background(Color.orange)
                        .cornerRadius(15, antialiased: true)
                })
                .frame(maxWidth: .infinity, alignment: .leading)
                .padding(.trailing, 10.0)
            }
        }
    }
}
```

とりあえず、こんな感じ。
この程度であればわざわざ@StateObjectを使わなくても良いようにも思いますが、後でいろいろやりたくなった時のために一応……