Firebase Model Framework Saladaを使ってUser Modelを作る
Firebaseについて実践的な内容を記事にしていないなと思い、普段私がアプリで採用してるUser Modelを紹介します。
この記事が紹介すること
- Saladaのインストール
- FirebaseでUser Modelを作る
- Firebase AuthとUser Modelを連携する
Saladaのインストール
ついに**Salada**がCococapodsに対応しました。
インストールには pod 1.4以上を使う必要があります。
まずcocoapodsをアップデートしましょう。
$ gem install cocoapods --pre
あとはいつも通りPodfileにこれを書くだけ
pod 'Salada'
これでインストールは完了です!
それぞれのファイルがどんな役割を持っているかについて詳しく聞きたい方はTwiiterなり、Qiitaに質問ください。喜んで回答します。
FirebaseでUser Modelを作る
早速User Modelを作って行きましょう。User.swiftでもいいんですが、Realmで同じUser Modelを作った時に名前が衝突するのが嫌だったのでそれ以来私はFirebase+User.swiftとしています。Firebase AuthにもUserと定義されているクラスがあるので何かしら名前空間を与えてあげた方がいいと思います。
 
今回はシンプルですがよく使いそうな構成を紹介します。
import Firebase
extension Firebase {
    class User: Object {
        @objc dynamic var name: String?
        @objc dynamic var thumbnailImage: File?
        let followers: Follower = []
        let friends: Friend = []
    }
}
ユーザーの名前とサムネイルとフォロー機能を提供します。
Fileを利用すればFirebaseStorageとシームレスに連携できます。
extension Firebase.User {
    static func current(_ completionHandler: @escaping ((Firebase.User?) -> Void)) {
        guard let user: User = Auth.auth().currentUser else {
            completionHandler(nil)
            return
        }
        Firebase.User.observeSingle(user.uid, eventType: .value, block: { (user) in
            guard let user: Firebase.User = user else {
                // FirebaseのDBにUserが保存されていない場合はログアウトする
                _ = try? Auth.auth().signOut()
                completionHandler(nil)
                return
            }
            completionHandler(user)
        })
    }
    // ユーザーをフォローする
    public func follow() {
        Firebase.User.current { (me) in
            guard let me = me else { return }
            self.followers.insert(me)
            me.friends.insert(self)
        }
    }
    // ユーザーをフォローをやめる
    public func unfollow() {
        Firebase.User.current { (me) in
            guard let me = me else { return }
            self.followers.remove(me)
            me.friends.remove(self)
        }
    }
}
Relationクラスを継承することで簡単に参照を作ることができます。
extension Firebase {
    // フォローしている
    class Follower: Relation<Firebase.User> {
    }
}
extension Firebase {
    // フォローされている
    class Friend: Relation<Firebase.User> {
    }
}
Userを保存する
let user: Firebase.User = Firebase.User()
user.name = "hoge"
user.thumbnailImage = File(data: UIImageJPEGRepresentation(image, 1))
user.save()
自分のユーザーにアクセスする
Firebase.User.current { user in
    // Do something
}
他のユーザーにアクセスする
Firebase.User.observeSingle("id", eventType: .value) { (user) in
    // Do something      
}
プロパティを更新
Firebase.User.current { user in
    user.name = "Update"
}
SaladaはUpdateのファンクションを用意していません。
プロパティを監視して値がセットされたタイミングでリアルタイムにFirebaseに保存されるようになっています。
なので新しい値をセットしてあげるだけです。なぜこのようになっているかと言うとFirebaseはオフラインでも使えるからです。
オフライン時のエラーのハンドリングが不要な訳です。そもそもFirebaseではタイムアウトと言う概念がありませんしエラーも返ってきません。オフラインの場合は、一時的にローカルに保存され、ネットワークに再接続された瞬間にFirebase本体と同期します。
Firebase AuthとUser Modelを連携する
実はこれが結構厄介で、Firebase Authは認証が完了するとAuthStateDidChangeと言うNotificationを飛ばしてくれるんですが、実際欲しいNotificationは認証が完了したあとユーザー作成されてからの方が嬉しい訳です。
Auth.auth().signIn(with: credential) { (authUser, error) in
    guard let authUser = authUser else {
        print(error)
        return
    }
    // AuthStateDidChangeはこの時点で飛んじゃう。
    Firebase.User.observeSingle(authUser.uid, eventType: .value) { user in
        if let user = user {
            // すでにユーザーがある場合はそのままログイン
        } else {
            // ない場合はユーザーを作る
            let newUser: Firebase.User = Firebase.User(id: authUser.uid)!
            newUser.name = authUser.displayName
            newUser.save { (_, error) in
                if let _ = error {
                    print(error)
                    return
                }
                // ここで自前のNotificationを飛ばしましょう。
            }
        }
    }
}
では、素敵なFirebase Lifeを送ってください。