Edited at

[Swift] macOSでFirebaseを使う ~ おまけ

More than 1 year has passed since last update.


単なるおまけです


DatabaseValue

こんなのあったらユニットテストに便利なんじゃないかなぁとかおもう。

処理が重すぎるので常用するには難ありか。


DatabaseValue.swift

import Foundation

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

public func isDatabaseValue(_ object: Any) -> Bool {

switch object {

case let array as [Any]: return isDatabaseValue(array)

case let dict as [AnyHashable: Any]: return isDatabaseValue(dict)

case _ as DatabaseValue: return true

default: return false
}
}

private func isDatabaseValue(_ array: [Any]) -> Bool {

return array.first { !isDatabaseValue($0) } == nil
}

private func isDatabaseValue(_ dict: [AnyHashable: Any]) -> Bool {

guard let dict = dict as? [String: Any] else { return false }

return dict.values.first { !isDatabaseValue($0) } == nil
}



DatabaseModel

意味ない気しかしない


DatabaseModel.swift

import Foundation

import FirebaseDatabase

protocol DatabaseModel {

var databaseKey: String { get }
}

extension DatabaseReference {

func child(_ model: DatabaseModel) -> DatabaseReference {

return child(model.databaseKey)
}
}



Result

ちょっと後で使うので最小限の実装。


Result.swift

enum Result<Value> {

case value(Value)

case error(Error)
}

extension Result {

init(_ f: () throws -> Value) {

do {

self = .value(try f())

} catch {

self = .error(error)
}
}
}

extension Result {

@discardableResult
func ifValue(_ f: (Value) -> Void) -> Result {

if case let .value(value) = self {

f(value)
}

return self
}

@discardableResult
func ifError(_ f: (Error) -> Void) -> Result {

if case let .error(error) = self {

f(error)
}

return self
}
}



DatabaseCodable

多分便利。

DataSnapshot.valueがAnyなため、処理をするための型変換が煩わしく、処理が見えにくくなる問題を解決する。

あくまでも処理を見えやすくするためのもの。

型変換自体のコードは当然必要。


DatabaseCodable.swift

import Foundation

import FirebaseDatabase

protocol DatabaseDecodable {

init(from: DataSnapshot) throws
}

protocol DatabaseEncodable {

func encode() throws -> Any
}

typealias DatabaseCodable = DatabaseEncodable & DatabaseDecodable

extension DatabaseQuery {

func observe<D: DatabaseDecodable>(_ type: DataEventType, type decodable: D.Type, with handler: @escaping (Result<D>) -> Void) -> DatabaseHandle {

return observe(type) { snapshot in

handler(Result({ try decodable.init(from: snapshot) }))
}
}

func observeArray<D: DatabaseDecodable>(_ type: DataEventType, type decodable: D.Type, with handler: @escaping (Result<[D]>) -> Void) -> DatabaseHandle {

return observe(type) { snapshot in

let value = Result {

try snapshot
.children
.compactMap { $0 as? DataSnapshot }
.compactMap { try decodable.init(from: $0) }
}

handler(value)
}
}
}

extension DatabaseReference {

func setEncodableValue<E: DatabaseEncodable>(_ value: E, with handler: @escaping ((Error?, DatabaseReference) -> Void) = { _,_ in }) {

do {

setValue(try value.encode(), withCompletionBlock: handler)

} catch {

handler(error, self)
}
}
}



使用例

上のDatabaseCodableを使用することでobserve(_:with:)に渡す関数が、値を変換する処理に目がいって、実際に何をしているのかがわかりづらいという問題を解消できる。


Memo.swift

enum MemoCodingError: Error {

case notMemo

case hasNotMemo

case hasNotDate
}

extension Memo: DatabaseCodable {

func encode() throws -> Any {

return ["memo": memo, "date": date]
}

init(from snapshot: DataSnapshot) throws {

guard let dict = snapshot.value as? [String: Any] else { throw MemoCodingError.notMemo }
guard let memo = dict["memo"] as? String else { throw MemoCodingError.hasNotMemo }
guard let date = dict["date"] as? TimeInterval else { throw MemoCodingError.hasNotDate }

self.dataKey = snapshot.key
self.memo = memo
self.date = date
}
}



MemoViewController.swift

extension MemoViewController {

private func registerObserver() {

guard let ref = userRef else { return }

let h0 = ref.observe(.childAdded, type: Memo.self) { result in

result
.ifValue { memo in

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)
}
.ifError { error in print(error) }
}
handles.append(h0)

let h1 = ref.observe(.childChanged, type: Memo.self) { result in

result
.ifValue { memo in

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.")
}
}
.ifError { error in print(error) }
}
handles.append(h1)
}
}


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

macOSでFirebaseを使う ~ Authentication編

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

macOSでFirebaseを使う ~ Realtime Database編