Swift: Multiple async requests in order. How to wait for previous request to finish?

空扰寡人 提交于 2021-02-07 20:30:45

问题


As part of the authentication process in my app, users can sign in using their Facebook account - I'm using the Facebook iOS SDK to handle this process. Once authentication is complete, I make a request to Facebook graph api to fetch the users profile data (This is the first async req). The second async request is also to Facebook graph api to request the users friends list who have the app installed.

The final and third request in this function makes a async POST request to an API I've developed to post all of the data collected from Facebook. Finally once this is complete the user is allowed in to the app. However this is not the case, it seems that the Facebook requests do not complete before the POST request to the API and it is therefor pushing up blank data. I don't mind in what order the first 2 requests to Facebook finish, however I NEED the data to be successfully posted to the API before allowing the user in to the app. I've tried using semaphores and Dispatch groups, however when looking at the console, things are not running the in the correct order and I can see from the API database that null values are being inserted.

Authentication Controller

 // Successful login, fetch faceook profile
            let group = DispatchGroup()
            group.enter()
            // Redirect to tab bar controller should not happen until fetchProfile() has finished 
            // Redirect should not happen if fetchProfile() errors
            self.fetchProfile() 
            group.leave()

            // Redirect to tab bar controller
            let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
            let tabBarController = storyboard.instantiateViewController(withIdentifier: "tabBarController") as! UITabBarController
            self.present(tabBarController, animated: true, completion: nil)

Updated Facebook Fetch Profile

 // Facebook Profile Request
    func fetchProfile() {

    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let managedContext = appDelegate.managedObjectContext
    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "User")
    let user = appDelegate.user
    var facebookFriends = [String?]()

    do {
        let results = try managedContext?.fetch(fetchRequest)
        fetchedUser = results![0] as? NSManagedObject
    }
    catch {
        print("Error fetching User entity")
        return
    }


    let group = DispatchGroup()
    print("Starting Step 1")
    group.enter()

    // Facebook Profile
    let parameters = ["fields": "id, email, first_name, last_name, picture.width(500).height(500), birthday, gender"]
    FBSDKGraphRequest(graphPath: "me", parameters: parameters).start { (connection, result, error) -> Void in

        if error != nil {
            print(error)
            return
        }

        let result = result as? NSDictionary

        if let providerID = result?["id"] as? String {
            user.provider_id = providerID
            self.fetchedUser!.setValue(providerID, forKey: "provider_id")
        }

        if let firstName = result?["first_name"] as? String {
            user.first_name = firstName
            self.fetchedUser!.setValue(firstName, forKey: "first_name")
        }

        if let lastName = result?["last_name"] as? String {
            user.last_name = lastName
            self.fetchedUser!.setValue(lastName, forKey: "last_name")
        }

        if let email = result?["email"] as? String {
            user.email = email
            self.fetchedUser!.setValue(email, forKey: "email")
        }

        if let picture = result?["picture"] as? NSDictionary, let data = picture["data"] as? NSDictionary, let url = data["url"] as? String {
            user.avatar = url
            self.fetchedUser!.setValue(url, forKey: "avatar")
        }

        if let birthday = result?["birthday"] as? String {
            user.birthday = birthday
            self.fetchedUser!.setValue(sqlDate, forKey: "birthday")
        }

        if var gender = result?["gender"] as? String {
            user.gender = gender
            self.fetchedUser!.setValue(gender, forKey: "gender")
        }

        group.leave()
        print("Step 1 Done")

        group.enter()
        print("Starting Step 2")

        // Facebook Friends Request
        FBSDKGraphRequest(graphPath: "me/friends", parameters: ["fields": "id, first_name, last_name, picture"]).start { (connection, result, error) -> Void in

            if error != nil {
                print(error)
                return
            }

            let result = result as! [String:AnyObject]

            for friend in result["data"] as! [[String:AnyObject]] {
                let id = friend["id"] as! String
                facebookFriends.append(id)
            }

            group.leave()
            print("Step 2 Done")

            // User POST Request
            var dictionary = self.fetchedUser?.dictionaryWithValues(forKeys: ["provider", "provider_id", "first_name", "last_name", "email", "avatar", "birthday", "gender"])

            if facebookFriends.count > 0 {
                dictionary?["friends"] = facebookFriends
            }

            let data = NSMutableDictionary()
            data.setValuesForKeys(dictionary!)

            //let semaphore = DispatchSemaphore(value: 2)
            group.enter()
            print("Starting Step 3")

            do {
                // Here "jsonData" is the dictionary encoded in JSON data
                let jsonData = try JSONSerialization.data(withJSONObject: data, options: .prettyPrinted)

                // Here "decoded" is of type `Any`, decoded from JSON data
                let decoded = try JSONSerialization.jsonObject(with: jsonData, options: [])

                // Final dict
                if let dictFromJSON = decoded as? [String:String] {

                    let endpoint = "http://endpoint.com/user"
                    let url = URL(string: endpoint)
                    let session = URLSession.shared
                    var request = URLRequest(url: url!)

                    request.httpMethod = "POST"
                    request.httpBody = try JSONSerialization.data(withJSONObject: dictFromJSON, options: [])
                    request.addValue("application/json", forHTTPHeaderField: "Accept")
                    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
                    session.dataTask(with: request, completionHandler: { (data, response, error) -> Void in

                        if error != nil {
                            //semaphore.signal()
                            group.leave()
                            print(error)
                            return
                        }

                        do {
                            // Save response
                            let json = try(JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String: AnyObject])

                            if let userID = json?["user_id"] {
                                user.user_id = userID as? Int
                                self.fetchedUser!.setValue(userID, forKey: "user_id")
                            }

                            if let friends = json?["friends"] , !(friends is NSNull){
                                user.friends = friends as? [String]
                                self.fetchedUser!.setValue(friends, forKey: "friends")
                            }

                            group.leave()
                            //semaphore.signal()

                        } catch let jsonError {
                            print(jsonError)
                            return
                        }

                    }).resume()

                }
            } catch {
                print(error.localizedDescription)
            }

            // Wait to async task to finish before moving on
            //_ = semaphore.wait(timeout: DispatchTime.distantFuture)
            print("Step 3 Done")
        }
    }
}

回答1:


Move the code after each closure inside the closure itself so that it waits until the code before it before running:

// Facebook Profile Request
func fetchProfile() {

    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let managedContext = appDelegate.managedObjectContext
    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "User")
    let user = appDelegate.user
    var facebookFriends = [String?]()

    do {
        let results = try managedContext?.fetch(fetchRequest)
        fetchedUser = results![0] as? NSManagedObject
    }
    catch {
        print("Error fetching User entity")
        return
    }


    let group = DispatchGroup()
    print("Starting Step 1")
    group.enter()

    // Facebook Profile
    let parameters = ["fields": "id, email, first_name, last_name, picture.width(500).height(500), birthday, gender"]
    FBSDKGraphRequest(graphPath: "me", parameters: parameters).start { (connection, result, error) -> Void in

        if error != nil {
            print(error)
            return
        }

        let result = result as? NSDictionary

        if let providerID = result?["id"] as? String {
            user.provider_id = providerID
            self.fetchedUser!.setValue(providerID, forKey: "provider_id")
        }

        if let firstName = result?["first_name"] as? String {
            user.first_name = firstName
            self.fetchedUser!.setValue(firstName, forKey: "first_name")
        }

        if let lastName = result?["last_name"] as? String {
            user.last_name = lastName
            self.fetchedUser!.setValue(lastName, forKey: "last_name")
        }

        if let email = result?["email"] as? String {
            user.email = email
            self.fetchedUser!.setValue(email, forKey: "email")
        }

        if let picture = result?["picture"] as? NSDictionary, let data = picture["data"] as? NSDictionary, let url = data["url"] as? String {
            user.avatar = url
            self.fetchedUser!.setValue(url, forKey: "avatar")
        }

        if let birthday = result?["birthday"] as? String {
            user.birthday = birthday
            self.fetchedUser!.setValue(sqlDate, forKey: "birthday")
        }

        if var gender = result?["gender"] as? String {
            user.gender = gender
            self.fetchedUser!.setValue(gender, forKey: "gender")
        }

        group.leave()
        print("Step 1 Done")

        group.enter()
        print("Starting Step 2")

        // Facebook Friends Request
        FBSDKGraphRequest(graphPath: "me/friends", parameters: ["fields": "id, first_name, last_name, picture"]).start { (connection, result, error) -> Void in

            if error != nil {
                print(error)
                return
            }

            let result = result as! [String:AnyObject]

            for friend in result["data"] as! [[String:AnyObject]] {
                let id = friend["id"] as! String
                facebookFriends.append(id)
            }

            group.leave()
            print("Step 2 Done")

            // User POST Request
            var dictionary = self.fetchedUser?.dictionaryWithValues(forKeys: ["provider", "provider_id", "first_name", "last_name", "email", "avatar", "birthday", "gender"])

            if facebookFriends.count > 0 {
                dictionary?["friends"] = facebookFriends
            }

            let data = NSMutableDictionary()
            data.setValuesForKeys(dictionary!)

            //let semaphore = DispatchSemaphore(value: 2)
            group.enter()
            print("Starting Step 3")

            do {
                // Here "jsonData" is the dictionary encoded in JSON data
                let jsonData = try JSONSerialization.data(withJSONObject: data, options: .prettyPrinted)

                // Here "decoded" is of type `Any`, decoded from JSON data
                let decoded = try JSONSerialization.jsonObject(with: jsonData, options: [])

                // Final dict
                if let dictFromJSON = decoded as? [String:String] {

                    let endpoint = "http://endpoint.com/user"
                    let url = URL(string: endpoint)
                    let session = URLSession.shared
                    var request = URLRequest(url: url!)

                    request.httpMethod = "POST"
                    request.httpBody = try JSONSerialization.data(withJSONObject: dictFromJSON, options: [])
                    request.addValue("application/json", forHTTPHeaderField: "Accept")
                    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
                    session.dataTask(with: request, completionHandler: { (data, response, error) -> Void in

                        if error != nil {
                            //semaphore.signal()
                            group.leave()
                            print(error)
                            return
                        }

                        do {
                            // Save response
                            let json = try(JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String: AnyObject])

                            if let userID = json?["user_id"] {
                                user.user_id = userID as? Int
                                self.fetchedUser!.setValue(userID, forKey: "user_id")
                            }

                            if let friends = json?["friends"] , !(friends is NSNull){
                                user.friends = friends as? [String]
                                self.fetchedUser!.setValue(friends, forKey: "friends")
                            }

                            group.leave()
                            //semaphore.signal()

                        } catch let jsonError {
                            print(jsonError)
                            return
                        }

                    }).resume()

                }
            } catch {
                print(error.localizedDescription)
            }

            // Wait to async task to finish before moving on
            //_ = semaphore.wait(timeout: DispatchTime.distantFuture)
            print("Step 3 Done")
        }
    }
}

Explanation: When you do asynchronous web requests, the closures are what's called escaping, which means they run after the function returns. For example, FBSDKGraphRequest.start takes an escaping closure, saves it, returns, and after it returns, runs the closure once the request finishes. This is intentional. Otherwise, it would be synchronous and block your code, which would cause your app to freeze (unless you use GCD to run the code asynchronously yourself).

TL;DR The closures are called after the function such as FBSDKGraphRequest.start returns, causing the next group to start before the one before it finishes. This can be fixed by placing them so that they run one after another.



来源:https://stackoverflow.com/questions/41428648/swift-multiple-async-requests-in-order-how-to-wait-for-previous-request-to-fin

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!