0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-07-17

単なるおまけです

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編

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?