Realtime Databaseへのアクセス
準備
Realtime databaseへのアクセスはDatabaseReference
を通じて行います。
DatabaseReference
はデータベースのキーに当たる部分以下を参照します。
root ─ Users ┬ $uid ┬ $id ┬ memo (String)
│ │ └ date (TimeInterval)
│ │
│ ├ $id ...
│
├ $uid ...
このスキーマですと、Users
、各$uid
、各$id
などに当たります。
今回作成中のメモアプリで使用するのは現在認証されているユーザー用のストレージ部分$uid
の部分です。
これをMemoViewController
のuserRef
プロパティとして保持します。
再掲
import Cocoa
import FirebaseAuth
import FirebaseDatabase
class MemoViewController: NSViewController {
private var userRef: DatabaseReference?
private var handles: [DatabaseHandle] = []
// 以下略
}
DatabaseHandle
はKVOのNSKeyValueObservation
と同じ役割で、監視を終了するときに用います。
ログインログアウトが自由に行えるようになっていますので、その度に'userRef'は書き換える必要があります。
MemoViewController
はログインすると表示され、ログアウトすると非表示になるので、viewWillAppear()
、viewWillDisappear
で書き換えを行うようにします。
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:
アクションとして実装します。
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/
のDatabaseReference
のchildByAutoId()
メソッドを用いてユニークなidを持った新しいDatabaseReference
を作成します。
このDatabaseReference
にsetValue(_:)
で値を設定します。
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()
メソッドを実装します。
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編