Edited at

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

More than 1 year has passed since last update.

前回の続きです


Realtime Database

実装に移る前に、mscOSプログラマがFirebaseの機構を理解するためのたった一つの言葉を書いておきます。

FirebaseはすごいKVC/KVO

これであなたはFirebaseをほとんど理解しました。


作るもの

個人用のメモ


仕様


  1. 認証済みユーザーのみ読み書きできる

  2. ほかのユーザーのメモは読み書きできない

  3. メモは「作成日時」「内容」で構成される

ごくシンプルに。


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",
}
}
}
}


やっていることは


  1. データベースへのアクセスは認証済みでなければならない


  2. 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の定義

メモのモデルを作っておきます。


Memo.swift

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を作ります。


MemoCellView.swift

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
}
}
}
}


日時の表示にはNSTableCellViewtextFieldを利用します。

memoが設定されたらそれを表示するだけです。


NSViewController

次にNSViewControllerのサブクラスMemoViewControllerを作ります。

xib付きで作成してください。

これもCocoaBindingsを利用します。


MemoViewController.swift

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は次のようにしました。

ちょっとアレですが、そこは目をつむってください。

一番上のNSTextFieldvalueFile's Ownermemoをbind、Continuously Updates Valueをチェック。

PostボタンのEnabledFile's OwnercanPostをbind。アクションは後ほど。

Sign outボタンのアクションをFirst RespondersignOut:に接続。

File's OwnermemoTableアウトレットにNSTableViewを接続。

NSTableViewdelegateおよびdataSourceFile's Ownerに接続。

NSCellViewCustom ClassMemoCellViewに、identifiermemoに設定。

NSCellView内にNSTextFieldを配置して、これをMemoCellViewmemoFieldアウトレットに接続。

元から付いてるNSTextFieldDateFormatterを設定。

AutolayoutもしくはAutoresizingはいい感じに設定してください。


NSTableViewDelegate & NSTableViewDataSource

NSTableViewDelegateおよびNSTableViewDataSourceは以下の通りです。


MemoViewController.swift

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
}
}


エラー処理をはしょってますが、普通の実装です。特に問題ないでしょう。


メインウインドウ

作ったMemoViewControllerMainWindowに貼り付けます。


MainWindowController.swift

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編

macOSでFirebaseを使う ~ Realtime Database編

macOSでFirebaseを使う ~ おまけ