Firebase Realtime Databaseを使った非同期処理の実装 - Swiftのasync/awaitでシンプルに！

`Firebase Realtime Database`を使ったデータ保存・読み込み処理を、Swiftの`async/await`でシンプルに実装する方法を紹介します。
## UserProfileの構造

まずは`UserProfile`の構造体を定義します。

```swift
struct UserProfile: Codable, Sendable {
    var nickName: String
    var startDate: Double  // Unixタイムスタンプ（秒単位）
    var follow: [String]

    init(nickName: String = "未設定", follow: [String] = [], startDate: Double = Date().timeIntervalSince1970) {
        self.nickName = nickName
        self.follow = follow
        self.startDate = startDate
    }
}
```

### 説明
- `nickName`：ユーザー名を保存。
- `startDate`：ユーザー開始日をUnixタイムスタンプで保存。
- `follow`：フォローしているユーザーのIDリスト。


 ## Firebaseからプロフィールを非同期に取得する

`Firebase Realtime Database`から`UserProfile`を取得する関数です。

```swift
@MainActor
class ProfilePostModel: ObservableObject {
    var profile: UserProfile = UserProfile()

    // 非同期にFirebaseからプロフィールをロードする関数
    func loadProfileFromDatabase(id: String) async throws -> UserProfile? {
        let ref = Database.database().reference()
        let userRef = ref.child("users").child(id)

        do {
            // Firebaseから非同期でデータを取得
            let snapshot = try await getValueAsync(ref: userRef)
            guard let value = snapshot as? [String: Any] else {
                print("データが存在しないか、フォーマットが不正です")
                return nil
            }

            // 取得したデータをUserProfileにデコード
            let jsonData = try JSONSerialization.data(with: value)
            let profile = try JSONDecoder().decode(UserProfile.self, from: jsonData)

            print("プロフィールのロードに成功しました: \(profile)")
            return profile

        } catch {
            print("プロフィールのロードに失敗しました: \(error.localizedDescription)")
            throw error
        }
    }

    // Firebaseから値を取得する補助関数
    func getValueAsync(ref: DatabaseReference) async throws -> Any? {
        try await withCheckedThrowingContinuation { continuation in
            ref.observeSingleEvent(of: .value, with: { snapshot in
                Task { @MainActor in
                    continuation.resume(returning: snapshot.value)
                }
            }, withCancel: { error in
                continuation.resume(throwing: error)
            })
        }
    }
}

```

### 説明
- `loadProfileFromDatabase`：指定した`id`に対応するプロフィールデータを取得し、`UserProfile`として返します。
- `getValueAsync`：`observeSingleEvent`を`async/await`でラップして、非同期処理に変換しています。
 ## Firebaseにプロフィールを非同期に保存する

次に、`UserProfile`をFirebaseに非同期に保存する関数です。

```swift
@MainActor
class ProfilePostModel: ObservableObject {
    var profile: UserProfile = UserProfile()

    // 非同期にFirebaseにプロフィールを保存する関数
    func saveProfileToDatabase() async {
        let ref = Database.database().reference()
        do {
            let userRef = ref.child("users").child(profile.nickName)
            let profileData = try JSONEncoder().encode(profile)
            if let profileDictionary = try JSONSerialization.jsonObject(with: profileData) as? [String: Any] {
                try await setValueAsync(ref: userRef, value: profileDictionary)
            } else {
                print("プロフィールデータの変換に失敗しました")
            }
            print("プロフィールを保存しました")
        } catch {
            print("プロフィールの保存に失敗しました: \(error.localizedDescription)")
        }
    }

    // FirebaseのsetValueメソッドをasync/awaitでラップする補助関数
    func setValueAsync(ref: DatabaseReference, value: Any) async throws {
        try await withCheckedThrowingContinuation { continuation in
            ref.setValue(value) { error, _ in
                if let error = error {
                    continuation.resume(throwing: error)
                } else {
                    continuation.resume()
                }
            }
        }
    }
}
```

### 説明
- `saveProfileToDatabase`：`UserProfile`を`Firebase Realtime Database`に保存します。
- `setValueAsync`：Firebaseの`setValue`メソッドを`async/await`でラップし、非同期処理を簡潔に実装します。

## まとめ

`async/await`を使うことで、Firebaseとの非同期通信が非常にシンプルになりました。コールバックベースのコードから脱却でき、可読性が高まり、メンテナンスがしやすくなります。