前回の続きです
Realtime Database
実装に移る前に、mscOSプログラマがFirebaseの機構を理解するためのたった一つの言葉を書いておきます。
FirebaseはすごいKVC/KVO
これであなたはFirebaseをほとんど理解しました。
作るもの
個人用のメモ
仕様
- 認証済みユーザーのみ読み書きできる
- ほかのユーザーのメモは読み書きできない
- メモは「作成日時」「内容」で構成される
ごくシンプルに。
Realtime Databaseの設定
仕様1.2.を実現するためにFirebase側で設定を行います。
Firebase consoleへ移動します。
「Detabase」から「Realtime Database」の「データベースを作成」を選択します。
セキュリティールールはすぐ変更しますので、どちらを選んでも構いません。
{
"rules": {
".read": "auth != null",
".write": "auth != null",
"users": {
"$uid": {
".read": "$uid == auth.uid",
".write": "$uid == auth.uid",
}
}
}
}
やっていることは
- データベースへのアクセスは認証済みでなければならない
-
users\$uid
以下へのアクセスはuidが$uid
のユーザーのみ許可
です。
rootの read, writeはこれでいいのか自信がないです。
Realtime Databaseの概要
Realtime Databaseの構造は、rootを根とする一つの巨大なDictionany
です。
Swift的にはこうなっていると考えると分かりやすいです。
protocol DatabaseValue {}
extension String: DatabaseValue {}
extension Int: DatabaseValue {}
extension Float: DatabaseValue {}
extension Double: DatabaseValue {}
extension Bool: DatabaseValue {}
extension Array: DatabaseValue where Element: DatabaseValue {}
extension Dictionary: DatabaseValue where Key == String, Value: DatabaseValue {}
struct RealtimeDatabase {
let root: DatabaseValue
}
詳細な仕様が見つけられてないので、もしかすると使用可能な型がまだあるかもしれません。
各要素へはKeypathでのアクセスのが可能です。
// Realtime Database のrootのReference
let root = Database.database().reference()
// キーによるアクセス
let config01 = root.child("room1").child("config")
// キーパスによるアクセス。セパレータは /
let config02 = root.child("room1/config")
値の設定はKVCのように行います。
root.child("room1").child("config").setValue(["limit": 1000])
値の取得は普通には出来ず、KVOのObserverのような設定が必要です。
// "room1/config"の変更を監視
let handler = root.child("room1").child("config").observe(.childChanged) { snapshot in
guard let dict = snapshot.value as? [String: Any] else { return }
guard let limit = dict["limit") as? Int else { return }
print(limit)
}
オブザーバはそのままでは値の取得が大変面倒くさい可読性が落ちるので、後ほどextensionを作ります。
スキーマ定義
フリーダムなNoSQLなので必要はないのですが、スキーマ定義はしておかないとあとで死にます問題があります。
root ─ Users ┬ $uid ┬ $id ┬ memo (String)
│ │ └ date (TimeInterval)
│ │
│ ├ $id ...
│
├ $uid ...
$uid
はユーザー固有のidです。
$id
はメモにユニークなidです。
各ユーザーが個別のメモのリスト(実際には辞書)を持っているようになっている。
各メモはメモ自身と作成日時を持っています。
Modelの定義
メモのモデルを作っておきます。
struct Memo {
// realtime database の key
let dataKey: String
let memo: String
let date: TimeInterval
}
extension Memo: Equatable {
static func == (lhs: Memo, rhs: Memo) -> Bool {
return lhs.dataKey == rhs.dataKey
}
}
dataKey
はスキーマ定義の$id
に当たるものです。
持っておくと便利なこともあったりなかったり。
ユーザーインターフェイスの作成
メモの表示はViewベースのNSTableView
を使います。
NSTableCellView
まずはNSTableCellView
のサブクラスMemoCellView
を作ります。
import Cocoa
class MemoCellView: NSTableCellView {
@IBOutlet private var memoField: NSTextField!
var memo: Memo? {
didSet {
textField?.objectValue = memo.map { Date(timeIntervalSince1970: $0.date) }
memoField.stringValue = memo?.memo ?? ""
}
}
static var identifier: NSUserInterfaceItemIdentifier {
return NSUserInterfaceItemIdentifier("memo")
}
override var backgroundStyle: NSView.BackgroundStyle {
didSet {
switch backgroundStyle {
case .dark:
memoField.textColor = .white
case .light, .raised, .lowered:
memoField.textColor = .controlTextColor
}
}
}
}
日時の表示にはNSTableCellView
のtextField
を利用します。
memo
が設定されたらそれを表示するだけです。
NSViewController
次にNSViewController
のサブクラスMemoViewController
を作ります。
xib付きで作成してください。
これもCocoaBindingsを利用します。
import Cocoa
import FirebaseAuth
import FirebaseDatabase
class MemoViewController: NSViewController {
private var userRef: DatabaseReference?
private var handles: [DatabaseHandle] = []
private var memos: [Memo] = []
@IBOutlet private var memoTable: NSTableView!
@objc dynamic private var memo: String = ""
@objc dynamic private var canPost: Bool {
return memo != ""
}
override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
if key == #keyPath(canPost) {
return [#keyPath(memo)]
}
return []
}
}
謎のプロパティがありますが、とりあえず無視してください。
memos
プロパティの内容をmemoTable
に表示するだけです。
見た目
UIは次のようにしました。

ちょっとアレですが、そこは目をつむってください。
一番上のNSTextField
のvalue
にFile's Owner
のmemo
をbind、Continuously Updates Value
をチェック。
PostボタンのEnabled
に File's Owner
のcanPost
をbind。アクションは後ほど。
Sign outボタンのアクションをFirst Responder
のsignOut:
に接続。
File's Owner
のmemoTable
アウトレットにNSTableView
を接続。
NSTableView
のdelegate
およびdataSource
をFile's Owner
に接続。
NSCellView
のCustom Class
をMemoCellView
に、identifier
をmemo
に設定。
NSCellView
内にNSTextField
を配置して、これをMemoCellView
のmemoField
アウトレットに接続。
元から付いてるNSTextField
にDateFormatter
を設定。
Autolayout
もしくはAutoresizing
はいい感じに設定してください。
NSTableViewDelegate & NSTableViewDataSource
NSTableViewDelegate
およびNSTableViewDataSource
は以下の通りです。
extension MemoViewController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let memoView = tableView.makeView(withIdentifier: MemoCellView.identifier, owner: nil) as? MemoCellView else {
fatalError("Can not get MemoCellView.")
}
memoView.memo = memos[row]
return memoView
}
}
extension MemoViewController: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return memos.count
}
}
エラー処理をはしょってますが、普通の実装です。特に問題ないでしょう。
メインウインドウ
作ったMemoViewController
をMainWindow
に貼り付けます。
import Cocoa
class MainWindowController: NSWindowController {
override var windowNibName: NSNib.Name {
return NSNib.Name("MainWindowController")
}
override func windowDidLoad() {
super.windowDidLoad()
self.contentViewController = MemoViewController()
}
}
気になるようでしたら前回MainWindowController.xib
に貼り付けたボタンは外してください。
外さなくても問題ないです。
次回
Realtime Databaseへのアクセスを行います。
macOSでFirebaseを使う ~ Realtime Database用準備編
macOSでFirebaseを使う ~ Authentication編