LoginSignup
2
3

More than 5 years have passed since last update.

[Swift] macOSでFirebaseを使う ~ Realtime Database編

Last updated at Posted at 2018-07-17

Realtime Databaseへのアクセス

準備

Realtime databaseへのアクセスはDatabaseReferenceを通じて行います。

DatabaseReferenceはデータベースのキーに当たる部分以下を参照します。

再掲
root ─ Users ┬ $uid ┬ $id ┬ memo (String)
             │      │   └ date (TimeInterval)
             │      │
             │      ├ $id ...
             │
             ├ $uid ...

このスキーマですと、Users、各$uid、各$idなどに当たります。
今回作成中のメモアプリで使用するのは現在認証されているユーザー用のストレージ部分$uidの部分です。

これをMemoViewControlleruserRefプロパティとして保持します。
再掲

MemoViewController.swift
import Cocoa
import FirebaseAuth
import FirebaseDatabase

class MemoViewController: NSViewController {

    private var userRef: DatabaseReference?
    private var handles: [DatabaseHandle] = []

    // 以下略
}

DatabaseHandleはKVOのNSKeyValueObservationと同じ役割で、監視を終了するときに用います。

ログインログアウトが自由に行えるようになっていますので、その度に'userRef'は書き換える必要があります。
MemoViewControllerはログインすると表示され、ログアウトすると非表示になるので、viewWillAppear()viewWillDisappearで書き換えを行うようにします。

MemoViewController.swift
extension MemoViewController {

    override func viewWillAppear() {

        guard let uid = Auth.auth().currentUser?.uid else { return }

        userRef = Database.database().reference().child("Users").child(uid)

        registerObserver()
    }

    override func viewWillDisappear() {

        handles.forEach { userRef?.removeObserver(withHandle: $0) }
        handles = []

        userRef = nil

        // 表示内容を空に
        memos = []
        memoTable.reloadData()
    }
}

Realtime Databaseへの書き込み

データの書き込みと削除を行います。
前回作ったPostボタンのアクションおよび delete:アクションとして実装します。

MemoViewController.swift
extension MemoViewController {

    @IBAction private func post(_: Any) {

        guard canPost else { fatalError("ここに来るとはなさけない。") }

        let value: [String: Any] = [
            "date": Date().timeIntervalSince1970,
            "memo": memo
        ]

        userRef?.childByAutoId().setValue(value)

        self.memo = ""
    }

    @IBAction private func delete(_: Any) {

        let selectedIndex = memoTable.selectedRow
        guard case 0..<memos.count = selectedIndex else { return }

        userRef?.child(memos[selectedIndex].dataKey).removeValue()
    }
}

書き込み

まずはRealtime Databaseへの書き込みです。

userRef?.childByAutoId().setValue(value)

先に作成した /Users/$uid/DatabaseReferencechildByAutoId()メソッドを用いてユニークなidを持った新しいDatabaseReferenceを作成します。
このDatabaseReferencesetValue(_:)で値を設定します。

setValue(_:)の引数の型はAnyのため何でも渡すことが出来ますが、書き込み不可の型の値を渡した場合は失敗します。
setValue(_:with:)を用いることでエラーハンドリングが可能です。
Date型など前回あげなかった型の中にも書き込み可能な型がありますが、取得が出来ないので、対称性を考えると使用しないほうがよいと思われます。

削除

次にデータの削除です。

userRef?.child(memos[selectedIndex].dataKey).removeValue()

データの取得のところで書きますが、取得したデータからMemo型の値を作成するときに、書き込み時に自動生成したユニークなidをdataKeyプロパティに設定しています。
このプロパティを用いて、参照先を特定してremoveValue()メソッドにて削除します。

post:アクションが出来たのでPostボタンに接続します。

Realtime databaseの監視

Realtime databaseでは値の取得は自由には行えません。
KVOのような仕組みを用いて、追加変更などの時にそのデータのコピー(snapshot)が取得できるようになっています。

viewWillAppear()メソッドにあったregisterObserver()メソッドを実装します。

MemoViewController.swift
extension MemoViewController {

    private func registerObserver() {

        guard let ref = userRef else { return }

        // 追加を監視
        let h0 = ref.observe(.childAdded) { snapshot in

            guard let dict = snapshot.value as? [String: Any] else { return }
            guard let memoString = dict["memo"] as? String else { return }
            guard let date = dict["date"] as? TimeInterval else { return }

            let memo = Memo(dataKey: snapshot.key, memo: memoString, date: date)

            let index = self.memos.index { aMemo in aMemo.date < memo.date } ?? self.memos.count
            let insertIndex = max(index, 0)

            self.memos.insert(memo, at: insertIndex)
            self.memoTable.insertRows(at: [insertIndex], withAnimation: .slideDown)
        }
        handles.append(h0)

        // 削除を監視
        let h1 = ref.observe(.childRemoved) { snapshot in

            guard let dict = snapshot.value as? [String: Any] else { return }
            guard let memoString = dict["memo"] as? String else { return }
            guard let date = dict["date"] as? TimeInterval else { return }

            let memo = Memo(dataKey: snapshot.key, memo: memoString, date: date)

            if let index = self.memos.index(of: memo) {

                self.memos.remove(at: index)
                self.memoTable.removeRows(at: [index], withAnimation: .slideUp)

            } else {

                print("deletion: memo is not found.")
            }
        }
        handles.append(h1)
    }
}

Realtime databaseの監視はDatabaseReferenceのスーパークラスであるDatabaseQueryクラスのobserve(_:with:)メソッドによって行います。
仕組みについてはKVOを知っている我々にとっては説明不要ですね。

第一引数はどのイベントを監視するかを指定する、enum DataEventTypeの値です。

  • childAdded
  • childRemoved
  • childChanged
  • childMoved
  • value

の5種類があります。最初の4つについては文字通りのものです。valueは、何でもかんでも監視します。

第二引数は対象のイベントが発生したときに呼び出される関数です。型は(DataSnapshot) -> Void
DataEventTypeがvalue以外の場合は変更などがあったデータの、valueの時は監視対象自身のDataSnapshotが渡されます。

DataSnapshotはそれが生成された時点でのデータのコピーです。
valueプロパティに値が、keyプロパティにキーが入っています。
valueの型がAnyなので面倒くさいです。

完成

完成したと思う。

DatabaseQuery

DatabaseQueryをさらっと流しましたが、これがFirebaseをすごいKVOたらしめてるクラスです
監視をするときに「最初の10件だけ」とか「ここからここまで」とか「ソートして」など、柔軟に監視対象を設定できます。

詳細はまだ調べてません!!

次回

次回はobserve(_:with:)が面倒くさい、可読性をあげる、です。

macOSでFirebaseを使う ~ Realtime Database用準備編

macOSでFirebaseを使う ~ Authentication編

macOSでFirebaseを使う ~ Realtime Database用準備編

macOSでFirebaseを使う ~ おまけ

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3