Swift:依次处理多个异步请求.如何等待上一个请求完成? [英] Swift: Multiple async requests in order. How to wait for previous request to finish?
问题描述
在我的应用程序中,作为身份验证过程的一部分,用户可以使用其Facebook帐户登录-我正在使用Facebook iOS SDK来处理此过程.身份验证完成后,我向Facebook图形api发出请求以获取用户个人资料数据(这是第一个异步请求).第二个异步请求也发送到Facebook graph api,以请求安装了该应用程序的用户朋友列表.
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.
此函数中的最后一个请求和第三个请求向我开发的API发送异步POST请求,以发布从Facebook收集的所有数据.最后,一旦完成,便允许用户使用该应用程序.但是事实并非如此,Facebook请求似乎在对API的POST请求之前没有完成,因此推高了空白数据.我不介意对Facebook的前两个请求以什么顺序完成,但是在允许用户进入应用程序之前,我需要将数据成功发布到API.我已经尝试过使用信号量和调度组,但是在查看控制台时,事情并没有以正确的顺序运行,而且我从API数据库中可以看到正在插入空值.
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.
身份验证控制器
// 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)
更新的Facebook提取个人资料
// 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")
}
}
}
推荐答案
在闭包自身内部的每个闭包之后移动代码,以使其在运行之前一直等到之前的代码:
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")
}
}
}
说明:当您执行异步Web请求时,闭包就是所谓的转义,这意味着它们在函数返回后运行.例如, FBSDKGraphRequest.start
进行转义的闭包,保存后返回,然后在 返回后在请求完成后运行闭包.这是故意的.否则,它将是同步的并阻塞您的代码,这将导致您的应用程序冻结(除非您使用GCD自己异步运行代码).
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在诸如 FBSDKGraphRequest.start
之类的函数返回之后调用闭包,从而导致下一个组在完成之前先于下一个组开始.可以通过放置它们以使它们一个接一个地运行来解决此问题.
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.
这篇关于Swift:依次处理多个异步请求.如何等待上一个请求完成?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!