1
votes

I have created a function getFriends that reads a User's friendlist from firestore and puts each friend in a LocalUser object (which is my custom user class) in order to display the friendlist in a tableview. I need the DispatchSemaphore.wait() because I need the for loop to iterate only when the completion handler inside the for loop is called. When loading the view, the app freezes. I know that the problem is that semaphore.wait() is called in the main thread. However, from reading DispatchQueue-tutorials I still don't understand how to fix this in my case. Also: do you see any easier ways to implement what I want to do?

This is my call to the function in viewDidLoad():

    self.getFriends() { (friends) in
        self.foundFriends = friends
        self.friendsTable.reloadData()
    }

And the function getFriends:

let semaphore = DispatchSemaphore(value: 0)

    func getFriends(completion: @escaping ([LocalUser]) -> ()) {
    var friendsUID = [String : Any]()
    database.collection("friends").document(self.uid).getDocument { (docSnapshot, error) in
        if error != nil {
            print("Error:", error!)
            return
        }
        friendsUID = (docSnapshot?.data())!
        var friends = [LocalUser]()
        let friendsIdents = Array(friendsUID.keys)

        for (idx,userID) in friendsIdents.enumerated() {
            self.getUser(withUID: userID, completion: { (usr) in
                var tempUser: LocalUser
                tempUser = usr
                friends.append(tempUser)
                self.semaphore.signal()
            })
            if idx == friendsIdents.endIndex-1 {
                print("friends at for loop completion:", friends.count)
                completion(friends)
            }
            self.semaphore.wait()
        }
    }
}

friendsUID is a dict with each friend's uid as a key and true as the value. Since I only need the keys, I store them in the array friendsIdents. Function getUser searches the passed uid in firestore and creates the corresponding LocalUser (usr). This finally gets appended in friends array.

1

1 Answers

2
votes

You should almost never have a semaphore.wait() on the main thread. Unless you expect to wait for < 10ms.

Instead, consider dispatching your friends list processing to a background thread. The background thread can perform the dispatch to your database/api and wait() without blocking the main thread.

Just make sure to use DispatchQueue.main.async {} from that thread if you need to trigger any UI work.

let semaphore = DispatchSemaphore(value: 0)

func getFriends(completion: @escaping ([LocalUser]) -> ()) {
    var friendsUID = [String : Any]()
    database.collection("friends").document(self.uid).getDocument { (docSnapshot, error) in
        if error != nil {
            print("Error:", error!)
            return
        }


    DispatchQueue.global(qos: .userInitiated).async {
        friendsUID = (docSnapshot?.data())!
        var friends = [LocalUser]()
        let friendsIdents = Array(friendsUID.keys)

        for (idx,userID) in friendsIdents.enumerated() {
            self.getUser(withUID: userID, completion: { (usr) in
                var tempUser: LocalUser
                tempUser = usr
                friends.append(tempUser)
                self.semaphore.signal()
            })
            if idx == friendsIdents.endIndex-1 {
                print("friends at for loop completion:", friends.count)
                completion(friends)
            }
            self.semaphore.wait()
        }
        // Insert here a DispatchQueue.main.async {} if you need something to happen
        // on the main queue after you are done processing all entries
    }
}