怠慢プログラマーの備忘録

怠慢でナマケモノなプログラマーの備忘録です。

【Swift】FirestoreのSnapshotListenerをObservableにした場合のListenerのDetach

firebase.google.com

上記の公式ドキュメントを参考にクエリによる条件一致に該当するスナップショットのリスナーをObservableにした場合、以下のような実装になります。

extension Reactive where Base: Firestore {
    
    func observe<T: EntityProtcol>(collectionKey: String, fieldKey: String, fieldValue: Any) -> Observable<[T]> {
        return Observable.create({ (observer: AnyObserver<[T]>) -> Disposable in
            self.base
                .collection(collectionKey)
                .whereField(fieldKey, isEqualTo: fieldValue)
                .addSnapshotListener { querySnapshot, error in
                    if let err = error {
                        observer.on(.error(err))
                        return
                    }
                    
                    if let snapshot = querySnapshot {
                        let entities: [T]
                            = snapshot.documents.compactMap {T.makeResult(document: $0.data())}
                        observer.on(.next(entities))
                    }
            }
            return Disposables.create()
        })
    }
}

上記のExtensionをRepositoryなどで使用、つまりリスナーをアタッチします。その為上記ではSubscriverが都度受信するのはObservableなEntitiesになります。

func observe(userId: String) -> Observable<[RoomEntity]> {
        return Firestore.firestore().rx.observe(collectionKey: "test_rooms", fieldKey: "user_id", fieldValue: userId) 
}

ただこの場合、リスナーをアタッチした際 addSnapshotListenerの戻り値ListenerRegistrationが受け取れないためデタッチすることができません。

なのでFirestore+RxのExtension側のメソッドobserve()をcallback関数としRepositoryなどの使用もとでクロージャを受け取ります。

    func observeCollection<T: EntityProtcol>(
        collectionKey: String, fieldKey: String,
        fieldValue: Any, callback: @escaping (ListenerRegistration) -> Void) -> Observable<[T]> {
        
        return Observable.create({ (observer: AnyObserver<[T]>) -> Disposable in
            let listener = self.base
                .collection(collectionKey)
                .whereField(fieldKey, isEqualTo: fieldValue)
                .addSnapshotListener { querySnapshot, error in
                    if let err = error {
                        observer.on(.error(err))
                        return
                    }
                    
                    if let snapshot = querySnapshot {
                        let entities: [T]
                            = snapshot.documents.compactMap {T.deserialize(document: $0.data())}
                        observer.on(.next(entities))
                    }
            }
            callback(listener)
            return Disposables.create()
        })
    }
func observe(userId: String) -> Observable<[RoomEntity]> {
        return Firestore
            .firestore()
            .rx.observeContainsCollection(
                collectionKey: "test_rooms",
                fieldKey: "user_id",
                fieldValue: userId) { [weak self] listener in
                    
            self?.listener = listener
        }
    }

そしてメンバ変数のlistenerに対してremove()する関数を定義して適切なタイミングでデタッチを行えます。

    func detach() {
        self.listener?.remove()
    }

詳解 Swift 第5版

詳解 Swift 第5版