【Swift】RepositoryパターンでRealmを使ってみた - 個人開発事例あり -

私のRealmの使用方法についてご紹介します。この記事では、Realmの基本的な定義と、個人開発での利用方法に焦点を当て、Repositoryパターンの実装について説明します。

https://apps.apple.com/jp/app/%E6%96%87%E5%AD%97%E8%AA%8D%E8%AD%98/id6473706737

今回は、こちらの個人開発で使用したRealmの定義とリポジトリーを切り出して説明します。もしよろしければ実際にアプリをダウンロードして使用してみてください。そして、星５をください🙇‍♂！！

では説明に進みます。

## Repositoryパターンを用いる目的
Repositoryパターンを採用する主な目的は、データ管理するツールであるRealmが将来的にサービス終了する可能性がある場合、データを簡単に他のツール（例：CoreDataやSwiftData）に移行しやすくできます。もしRealmで定義したモデルクラスをViewControllerなどで直接使用している場合、ツールの変更に伴い、多くのコードを書き直す必要があります。しかし、Repositoryパターンを利用してRealmモデルを構造体に変換するRepositoryクラスを作成すれば、移行時に修正が必要なコードを最小限に抑えることができます。

## モデル定義
以下に、実際のコードの例を挙げ、その説明を加えます。

### CharacterRecognition 構造体

```swift
struct CharacterRecognition {
    var uuidString = UUID().uuidString
    var title: String
    var content: String
    var createdAt: Date = Date()
    var uuid: UUID? {
        UUID(uuidString: uuidString)
    }
}
```


### RealmCharacterRecognition クラス

```swift
class RealmCharacterRecognition: Object {
    @Persisted(primaryKey: true)
    var uuidString = ""
    @Persisted
    var title: String
    @Persisted
    var content: String
    @Persisted
    var createdAt: Date
    var uuid: UUID? {
        UUID(uuidString: uuidString)
    }
    
    convenience init(title: String, text: String, createdAt: Date) {
        self.init()
        self.title = title
        self.content = text
        self.createdAt = createdAt
    }
}
```

`CharacterRecognition`と`RealmCharacterRecognition`は、それぞれデータのモデル定義を行っています。`RealmCharacterRecognition`はRealmでのデータ管理で用いており、ViewControllerなどで直接使用することは避けます。データを保存する際は`RealmCharacterRecognition`に変換して保存し、ViewControllerで使用する際は`CharacterRecognition`に変換して利用するように実装しました。

## RealmCharacterRecognition ⇔ CharacterRecognition　の変換

```swift
private extension CharacterRecognition {
    // RealmCharacterRecognitionからCharacterRecognitionへの変換
    init(managedObject: RealmCharacterRecognition) {
        self.uuidString = managedObject.uuidString
        self.title = managedObject.title
        self.content = managedObject.content
        self.createdAt = managedObject.createdAt
    }

    // CharacterRecognitionからRealmCharacterRecognitionへの変換
    func managedObject() -> RealmCharacterRecognition {
        let realmCharacterRecognition = RealmCharacterRecognition()
        realmCharacterRecognition.uuidString = self.uuidString
        realmCharacterRecognition.title = self.title
        realmCharacterRecognition.content = self.content
        realmCharacterRecognition.createdAt = self.createdAt
        return realmCharacterRecognition
    }
}
```
`CharacterRecognition`構造体と`RealmCharacterRecognition`クラスの相互変換を行うための拡張です。以下の実装を行うことによって、`RealmCharacterRecognition` ⇔ `CharacterRecognition`　の相互変換が楽に実装できます。

## Repositoryクラスの作成
### CharacterRecognitionRepository クラス

```swift
final class CharacterRecognitionRepository {
    private let realm = try! Realm()
    
    // 文字認識データの読み込み
    func loadCharacterRecognition(ascending: Bool) -> [CharacterRecognition] {
        // Realmから文字認識データを取得
        let realmCharacterRecognitions = realm
            .objects(RealmCharacterRecognition.self)
            .sorted(byKeyPath: "createdAt", ascending: ascending)
        
        // Realmオブジェクトを構造体に変換して配列に格納
        let realmCharacterRecognitionsArray = Array(realmCharacterRecognitions)
        let characterRecognitions = realmCharacterRecognitionsArray.map { CharacterRecognition(managedObject: $0) }
        
        return characterRecognitions
    }
    
    // 文字認識データの検索
    func loadCharacterRecognition(ascending: Bool, text: String) -> [CharacterRecognition] {
        // Realmから文字認識データを取得し、テキストでフィルタリング
        let realmCharacterRecognitions = realm
            .objects(RealmCharacterRecognition.self)
            .sorted(byKeyPath: "createdAt", ascending: ascending)
            .filter("content CONTAINS '\(text)' OR title CONTAINS '\(text)'")
        
        // Realmオブジェクトを構造体に変換して配列に格納
        let realmCharacterRecognitionsArray = Array(realmCharacterRecognitions)
        let characterRecognitions = realmCharacterRecognitionsArray.map { CharacterRecognition(managedObject: $0) }
        
        return characterRecognitions
    }
    
    // 文字認識データの追加
    func appendCharacterRecognition(characterRecognition: CharacterRecognition) {
        try! realm.write {
            let realmCharacterRecognition = characterRecognition.managedObject()
            realm.add(realmCharacterRecognition)
        }
    }
    
    // 文字認識データの更新
    func updateCharacterRecognition(characterRecognition: CharacterRecognition) {
        try! realm.write {
            let realmCharacterRecognition = realm.object(ofType: RealmCharacterRecognition.self, forPrimaryKey: characterRecognition.uuidString)
            realmCharacterRecognition?.title = characterRecognition.title
            realmCharacterRecognition?.content = characterRecognition.content
        }
    }

    // 文字認識データの削除
    func removeCharacterRecognition(characterRecognition: CharacterRecognition) {
        guard let countHistory = realm.object(
            ofType: RealmCharacterRecognition.self,
            forPrimaryKey: characterRecognition.uuidString
        ) else { return }
        
        // データを削除
        try! realm.write {
            realm.delete(countHistory)
        }
    }
}
```

このRepositoryクラスは、文字認識データの管理を行い、データの読み込み、検索、追加、更新、削除を行います。さらに、構造体とRealmモデルの相互変換もこのクラスで行います。

## まとめ
この記事では、Realmの基本的な使い方と、Repositoryパターンを利用したデータ管理のアプローチについて説明しました。この方法を取り入れることで、将来的なデータストレージツールの変更にも柔軟に対応できるようになります。ぜひ参考にしてみてください。

## 余談
こちらの方法は、アプリ道場サロンというオンラインサロンのコードレビュー会という勉強会で教えていただきました。
iOSでのアプリ開発に興味のある方がぜひ！おすすめです！

https://community.camp-fire.jp/projects/view/281055