1

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)
        }
    }
}
Alexander Khitev
  • 6,417
  • 13
  • 59
  • 115
  • If you have 85 observers you may be doing it wrong. However, the code included in your question and the description of the use case doesn't really indicate what's happening. Secondly, just because you add 5 observers and then remove 5 observers doesn't mean the *right* observers were added or removed. i.e. you could be removing the wrong observer when the ViewController goes out of scope. There are a lot of external calls in your code that could also be creating the issue. Please review [How to create a Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) – Jay Feb 18 '18 at 14:24
  • @Jay Sorry, you misunderstood me, I do not have 85 observers, but this is the handle figure 85, and there were only two observers, now I'll write a more complete example. – Alexander Khitev Feb 18 '18 at 14:27
  • @Jay Please have a look at Update 2; Also I add observers to ChatViewController, and when ChatViewController deinit, and I open the profile, and after the data deletion the observer handler should work, it does not work, although I output to logs that the observer is just the right one. – Alexander Khitev Feb 18 '18 at 14:39
  • Gotcha. However, my comment still applies. The handles are seemingly out of control and may not be implemented in a way where they would consistently work - we don't know what the external calls do. I would suggest storing the handles in a global array (for example) and add and remove as needed. It keeps it organized and in one place. See my example [here](https://stackoverflow.com/questions/43634926/with-firebase-swift-removeobserverwithhandle-does-not-remove-the-observer) for another strategy that keeps it tight. – Jay Feb 18 '18 at 14:40
  • On first glance, you've got a lot of unnecessary code. If you add an observer to a node within a view controller, store the handle as a class var of the view controller. When the controller is closing, use that var (handle) to remove the observer. As it is, you've got classes that store references to handles and I would bet that because of the code, your handles are getting overwritten and/or removed at the wrong time; i.e. ViewController1 closes but has accidentally stored the handle to ViewController2 so when it closes, ViewController2's observer is removed. Way to much code to really tell. – Jay Feb 18 '18 at 14:50
  • @Jay I'll try to save the handle as a variable in the ViewController and I will not change the whole class for now, I will write about the result. Also I want to add my current code, worked before. – Alexander Khitev Feb 18 '18 at 14:58
  • @Jay I made a terrible code, but did it to check the changes quickly, and began to store the handle in the ViewControllers, but the result was the same. – Alexander Khitev Feb 18 '18 at 15:28
  • I just noticed you are defining your handles like this *var handle: UInt = 0* when they should be defined like this *var handle: DatabaseHandle* or like this *var firebaseHandle: UInt8 = 0* – Jay Feb 18 '18 at 15:48
  • Oh - and ensure you are removing listeners in the ViewControllers viewDidDisappear function using the FirebaseHandle. See [tips](https://firebase.googleblog.com/2015/10/best-practices-for-ios-uiviewcontroller_6.html) – Jay Feb 18 '18 at 15:59
  • @Jay I saw it but I need to on some ViewControllers, the UI was updated after the user returned back, so I delete on some ViewControllers in deinit and not in the viewDidDisappear – Alexander Khitev Feb 18 '18 at 16:15

1 Answers1

0

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)
            }
        }
    }

}
Alexander Khitev
  • 6,417
  • 13
  • 59
  • 115