Search code examples
iosswiftfirebasefirebase-realtime-databasefirebase-queue

When Firebase query removes Observer with handle, other observers are not called


I'm monitoring the state of some data from several UIViewControllers. When the UIViewController deinit happens, I delete the specific observer with the handle. Now I noticed that after removing query observer from one UIViewController, in the future query observer on another UIViewController is not called, although the data is changing. Please tell me how it can be fixed?

  open func observeUserFriendshipEnd(_ observer: FirebaseObserverDelegate, isObserve: Bool, friendID: String, endHandler: ((_ removedFriendModel: FriendModel) -> Void)?, fail: ((_ error: Error) -> Void)?) {
        let observerID = observer.observerID
        let realmManager = RealmManager()
        guard let currentUserID = realmManager.getCurrentUser()?.id else { return }
        let query = Database.database().reference().child(FriendsPaths.MainGateways.friends.description).child(currentUserID).child(FriendsPaths.SubGateways.userFriends.description).queryOrdered(byChild: "friendID").queryEqual(toValue: friendID)

        if !isObserve {
            guard let handle = self.userFriendshipEndObservers[observerID] else { return }
            query.removeObserver(withHandle: handle)
            self.userFriendshipEndObservers[observerID] = nil
            // system
            removeObserverModel(observerID, handle: handle)
            return
        }

        DispatchQueue.global(qos: .background).async {
            var isContinue = true

            self.queue.sync {
                if self.userFriendshipEndObservers[observerID] != nil {
                    isContinue = false
                }
            }
            guard isContinue else { return }

            var handle: UInt = 0

            handle = query.observe(.childRemoved, with: { (snap) in
                if snap.value is NSNull {
                    return
                }                
                guard let dict = snap.value as? [String : Any] else { return }
                guard let removedFriendModel = Mapper<FriendModel>().map(JSON: dict) else { return }
                let friendRealmManager = FriendRealmManager()
                friendRealmManager.removeFriend(removedFriendModel.friendID)
                if removedFriendModel.friendID == friendID {
                    endHandler?(removedFriendModel)
                }
            }, withCancel: { (error) in
                fail?(error)
            })
            self.queue.sync {
                self.userFriendshipEndObservers[observerID] = handle
                self.addObserver(observerID, handle: handle, query: query, ref: nil)
            }
        }
    }

Update: I output to logs, handles, and everything is correctly added and deleted. For example ProfileViewController had 33 handle, ChatViewController had 85 handle, after ChatViewController deinit, removed observer with 85 handle, it turns out there was observer with 33 handle, but it is not called.

Update 1 how to know: - how many observers has firebase reference?

Update 2

class ProfileUserFormVC: FormViewController {

    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()
        addObservers()
    }

    deinit {
        removeObservers()
        NotificationCenter.default.removeObserver(self)
    }

}

// MARK: - Obsevers

extension ProfileUserFormVC {

    private func addObservers() {
        guard let userID = self.userID else { return }
        MatchObserverManager.shared.observeNewMatchWithUser(self, isObserve: true, userID: userID, completion: { [weak self] (match) in
            // do something
        }) { (error) in

        }

        MatchObserverManager.shared.observeUserMatchDeleting(self, isObserve: true, userID: userID, completion: { [weak self] (removedMatch) in
            // do something
            }, fail: nil)

        FriendsObserver.shared.observeUserFriendshipEnd(self, isObserve: true, friendID: userID, endHandler: { [weak self] (removedFriend) in
            DispatchQueue.main.async {
                // do something with ui
            }
            }, fail: nil)
    }

    private func removeObservers() {
        guard let userID = user?.id else { return }
        MatchObserverManager.shared.observeNewMatchWithUser(self, isObserve: false, userID: userID, completion: nil, fail: nil)
        MatchObserverManager.shared.observeUserMatchDeleting(self, isObserve: false, userID: userID, completion: nil, fail: nil)
        FriendsObserver.shared.observeUserFriendshipEnd(self, isObserve: false, friendID: userID, endHandler: nil, fail: nil)
    }

}

Managers

class FriendsObserver: FirebaseObserver {

    static let shared = FriendsObserver()
    private override init() {
        super.init()
    }

    // MARK: - Queues

    private let queue = DispatchQueue(label: "com.myapp.FriendsObserver.queue")

    // MARK: - Data

    private var userFriendshipEndObservers = [String : UInt]()

    open func observeUserFriendshipEnd(_ observer: FirebaseObserverDelegate, isObserve: Bool, friendID: String, endHandler: ((_ removedFriendModel: FriendModel) -> Void)?, fail: ((_ error: Error) -> Void)?) {
        let observerID = observer.observerID
        let realmManager = RealmManager()
        guard let currentUserID = realmManager.getCurrentUser()?.id else { return }
        let query = Database.database().reference().child(FriendsPaths.MainGateways.friends.description).child(currentUserID).child(FriendsPaths.SubGateways.userFriends.description).queryOrdered(byChild: "friendID").queryEqual(toValue: friendID)

        if !isObserve {
            guard let handle = self.userFriendshipEndObservers[observerID] else { return }
            query.removeObserver(withHandle: handle)
            self.userFriendshipEndObservers[observerID] = nil
            // system
            removeObserverModel(observerID, handle: handle)
            return
        }

        DispatchQueue.global(qos: .background).async {
            var isContinue = true

            self.queue.sync {
                if self.userFriendshipEndObservers[observerID] != nil {
                    isContinue = false
                }
            }
            guard isContinue else { return }

            var handle: UInt = 0

            handle = query.observe(.childRemoved, with: { (snap) in
                if snap.value is NSNull {
                    return
                }                
                guard let dict = snap.value as? [String : Any] else { return }
                guard let removedFriendModel = Mapper<FriendModel>().map(JSON: dict) else { return }
                let friendRealmManager = FriendRealmManager()
                friendRealmManager.removeFriend(removedFriendModel.friendID)
                if removedFriendModel.friendID == friendID {
                    endHandler?(removedFriendModel)
                }
            }, withCancel: { (error) in
                fail?(error)
            })

            self.queue.sync {
                self.userFriendshipEndObservers[observerID] = handle
                self.addObserver(observerID, handle: handle, query: query, ref: nil)
            }
        }
    }

}

Update 3 I did a quick change, although it's not right to do it, but I returned the handle to the handler after it was received, then I assigned a variable to the ViewController, and during deinit I also deleted the observer. I got the same result. That is, if I delete one observer with handle, then the second one does not work. Before that, my code worked in its original form.

open func observeUserFriendshipEnd(_ observer: FirebaseObserverDelegate, isObserve: Bool, friendID: String, handle: UInt?, handleHandler: ((_ handle: UInt) -> Void)?, endHandler: ((_ removedFriendModel: FriendModel) -> Void)?, fail: ((_ error: Error) -> Void)?) {
    let observerID = observer.observerID
    let realmManager = RealmManager()
    guard let currentUserID = realmManager.getCurrentUser()?.id else { return }
    let query = Database.database().reference().child(FriendsPaths.MainGateways.friends.description).child(currentUserID).child(FriendsPaths.SubGateways.userFriends.description).queryOrdered(byChild: "friendID").queryEqual(toValue: friendID)

    if !isObserve {
        guard let _handle = handle else { return }
        query.removeObserver(withHandle: _handle)
        return
    }

    DispatchQueue.global(qos: .background).async {
        var isContinue = true

        self.queue.sync {
            if self.userFriendshipEndObservers[observerID] != nil {
                isContinue = false
            }
        }
        guard isContinue else { return }

        var handle: UInt = 0

        handle = query.observe(.childRemoved, with: { (snap) in
            if snap.value is NSNull {
                return
            }                
            guard let dict = snap.value as? [String : Any] else { return }
            guard let removedFriendModel = Mapper<FriendModel>().map(JSON: dict) else { return }
            let friendRealmManager = FriendRealmManager()
            friendRealmManager.removeFriend(removedFriendModel.friendID)
            if removedFriendModel.friendID == friendID {
                endHandler?(removedFriendModel)
            }
        }, withCancel: { (error) in
            fail?(error)
        })

        handleHandler?(handle)
        self.queue.sync {
            self.userFriendshipEndObservers[observerID] = handle
            self.addObserver(observerID, handle: handle, query: query, ref: nil)
        }
    }
}

Solution

  • It was my mistake in the code, I changed FriendsObserver and now everything works.

    class FriendsObserver: FirebaseObserver {
    
        static let shared = FriendsObserver()
        private override init() {
            super.init()
        }
    
        // MARK: - Queues
    
        private let queue = DispatchQueue(label: "com.myapp.FriendsObserver.queue")
    
        // MARK: - Data
    
        private var userFriendshipStartObservers = [String : DatabaseHandle]()
        private var userFriendshipEndObservers = [String : DatabaseHandle]()
    
    
        open func observeSpecificUserFriendshipStart(_ observer: FirebaseObserverDelegate, isObserve: Bool, userID: String, startHandler: ((_ friend: FriendModel) -> Void)?, fail: ((_ error: Error) -> Void)?) {
            let observerID = observer.observerID
    
            let realmManager = RealmManager()
            let timestamp = Date().currentTimestamp
            guard let currentUserID = realmManager.getCurrentUser()?.id else { return }
            let query = Database.database().reference().child(FriendsPaths.MainGateways.friends.description).child(currentUserID).child(FriendsPaths.SubGateways.userFriends.description).queryOrdered(byChild: "friendID").queryEqual(toValue: userID)
    
            if !isObserve {
                guard let handle = userFriendshipStartObservers[observerID] else { return }
                query.removeObserver(withHandle: handle)
                userFriendshipStartObservers[observerID] = nil
    
                // system
                removeObserverModel(observerID, handle: handle)
                return
            }
    
            DispatchQueue.global(qos: .background).async {
                var isContinue = true
    
                self.queue.sync {
                    if self.userFriendshipStartObservers[observerID] != nil {
                        isContinue = false
                    }
                }
                guard isContinue else { return }
    
    
                var handle: DatabaseHandle = 0
                handle = query.observe(.childAdded, with: { (snapshot) in
                    guard snapshot.exists() else { return }
                    guard let dict = snapshot.value as? [String : Any] else { return }
                    guard let friendModel = Mapper<FriendModel>().map(JSON: dict) else { return }
    
                    guard timestamp < friendModel.friendshipTimeStamp else { return }
    
                    if friendModel.friendID == userID {
                        startHandler?(friendModel)
                    }
                }, withCancel: { (error) in
                    fail?(error)
                })
    
                self.queue.sync {
                    self.userFriendshipStartObservers[observerID] = handle
                    self.addObserver(observerID, handle: handle, query: query, ref: nil)
                }
            }
        }
    
        /// Only one observer on one object
        open func observeUserFriendshipEnd(_ observer: FirebaseObserverDelegate, isObserve: Bool, friendID: String, endHandler: ((_ removedFriendModel: FriendModel) -> Void)?, fail: ((_ error: Error) -> Void)?) {
            let observerID = observer.observerID
            let realmManager = RealmManager()
            guard let currentUserID = realmManager.getCurrentUser()?.id else { return }
            let query = Database.database().reference().child(FriendsPaths.MainGateways.friends.description).child(currentUserID).child(FriendsPaths.SubGateways.userFriends.description).queryOrdered(byChild: "friendID").queryEqual(toValue: friendID)
    
            if !isObserve {
                guard let handle = userFriendshipEndObservers[observerID] else { return }
                query.removeObserver(withHandle: handle)
                userFriendshipEndObservers[observerID] = nil
    
                // system
                removeObserverModel(observerID, handle: handle)
                return
            }
    
            DispatchQueue.global(qos: .background).async {
                var isContinue = true
    
                self.queue.sync {
                    if self.userFriendshipEndObservers[observerID] != nil {
                        isContinue = false
                    }
                }
                guard isContinue else { return }
    
                var handle: DatabaseHandle = 0
    
                handle = query.observe(.childRemoved, with: { (snap) in
                    guard snap.exists() else { return }
                    guard let dict = snap.value as? [String : Any] else { return }
                    guard let removedFriendModel = Mapper<FriendModel>().map(JSON: dict) else { return }
                    let friendRealmManager = FriendRealmManager()
                    friendRealmManager.removeFriend(removedFriendModel.friendID)
                    if removedFriendModel.friendID == friendID {
                        endHandler?(removedFriendModel)
                    }
                }, withCancel: { (error) in
                    fail?(error)
                })
    
                self.queue.sync {
                    self.userFriendshipEndObservers[observerID] = handle
                    self.addObserver(observerID, handle: handle, query: query, ref: nil)
                }
            }
        }
    
    }