単なるおまけです
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編