【发布时间】:2021-08-01 07:04:54
【问题描述】:
所以我的目标是根据网络调用是否有错误来更新 UI。目前,我的网络调用成功,尽管网络调用成功,但更新 UI 的完成处理程序未能显示正确的 UI。
这是我当前的网络 API 调用:
func makeRefundRequest(refundMade: @escaping (_ done: Bool) -> Void) {
getStripePaymentIntentID { (paymentid) in
guard let id = paymentid,
let url = URL(string: "https://us-central1-xxxxx-41f12.cloudfunctions.net/createRefund") else {
refundMade(false)
return
}
let json: [String: Any] = ["payment_intent": id]
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: json)
let task = URLSession.shared.dataTask(with: request) { [weak self] (data, response, error) in
if let response = response as? HTTPURLResponse,
response.statusCode == 200,
let data = data,
let _ = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
refundMade(true)
} else {
if error != nil {
self?.showAlert(title: "Refund Request Error", message: "There was an error making the refund request. Please check your connection and try again.")
}
refundMade(false)
}
}
task.resume()
}
}
这就是我决定在失败情况下更新 UI 之前的情况,这是有道理的,因为当事情真的失败时,你不希望看到 UI 更新就好像事情已经成功了。
所以我有处理退款和更新 UI 的功能,我将专门添加我遇到问题的完成处理程序:
let cancelPurchase = UIAlertAction(title: "Cancel Purchase", style: .default) { (purchaseCancel) in
self.viewPurchaseButton.isHidden = true
self.cancelPurchaseButton.isHidden = true
self.refundLoading.alpha = 1
self.refundLoading.startAnimating()
self.getEventDocumentID { (id) in
guard let id = id else { return }
self.makeRefundRequest { (done) in
if done == false {
DispatchQueue.main.async {
self.refundLoading.stopAnimating()
self.refundLoading.alpha = 0
self.viewPurchaseButton.isHidden = false
self.cancelPurchaseButton.isHidden = false
}
return
} else {
let group = DispatchGroup()
self.db.collection("student_users/\(user.uid)/events_bought/\(id)/guests").getDocuments { (querySnapshot, error) in
guard error == nil else { return }
guard querySnapshot?.isEmpty == false else { return }
for guest in querySnapshot!.documents {
let name = guest.documentID
let batch = self.db.batch()
let docRef = self.db.document("student_users/\(user.uid)/events_bought/\(id)/guests/\(name)")
batch.deleteDocument(docRef)
group.enter()
batch.commit { (err) in
guard err == nil else { return }
group.leave()
}
}
}
group.notify(queue: .main) {
DispatchQueue.main.asyncAfter(deadline: .now()+2) {
self.db.document("student_users/\(user.uid)/events_bought/\(id)").delete { (error) in
guard error == nil else { return }
self.refundLoading.stopAnimating()
self.refundLoading.alpha = 0
self.ticketFormButton.isHidden = false
self.cancelPurchaseButton.isHidden = true
self.viewPurchaseButton.isHidden = true
}
}
}
}
}
}
}
所以在上面写着if done == false 的地方,我基本上希望用户界面恢复到之前的状态,以便用户可以清楚地看到退款失败。现在,如果我在没有 DispatchQueue.main.async 调用的情况下执行此操作,应用程序将崩溃并显示一个线程错误,紫色突出显示“self.refundLoading.stopAnimating() 只能在主线程上调用”。
在我在if done == false {} 块中实现额外的异步调用之前,这工作正常,但尽管如此,我需要这个代码块。当我当前运行它时,网络调用总是成功,这意味着完成调用将是true,因此它应该显示正确的 UI。相反,它显示的 UI 好像网络调用失败并且没有从 Firestore 中删除。我该如何解决这个线程/Firestore 问题?
更新 -> 所以我查阅了更多文章并决定在我的警报操作中将[weak self] 添加到makeRefundRequest 调用中,并决定删除@987654333 的第一个调用@ 因为调用它两次显然会导致应用程序崩溃。
func makeRefundRequest(refundMade: @escaping (_ done: Bool) -> Void ) {
getStripePaymentIntentID { (paymentid) in
guard let id = paymentid,
let url = URL(string: "https://us-central1-xxxxx-41f12.cloudfunctions.net/createRefund") else {
return
}
let json: [String: Any] = ["payment_intent": id]
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: json)
let task = URLSession.shared.dataTask(with: request) { [weak self] (data, response, error) in
if let response = response as? HTTPURLResponse,
response.statusCode == 200,
let data = data,
let _ = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
refundMade(true)
} else {
if error != nil {
self?.showAlert(title: "Refund Request Error", message: "There was an error making the refund request. Please check your connection and try again.")
}
refundMade(false)
}
}
task.resume()
}
}
这是在警报操作中调用的实际方法本身:
func showFailureUI() {
DispatchQueue.main.async {
self.refundLoading.stopAnimating()
self.refundLoading.alpha = 0
self.viewPurchaseButton.isHidden = false
self.cancelPurchaseButton.isHidden = false
}
}
//Handles the action of cancelling the purchase
@IBAction func cancelPurchasePressed(_ sender: UIButton) {
guard let nameOfEvent = selectedEventName else { return }
guard let user = Auth.auth().currentUser else { return }
let alertForCancel = UIAlertController(title: "Cancel Purchase", message: "Are you sure you want to cancel your purchase of a ticket to \(nameOfEvent)? You will receive full reimbursement of what you paid within 5 - 10 days.", preferredStyle: .alert)
let cancelPurchase = UIAlertAction(title: "Cancel Purchase", style: .default) { (purchaseCancel) in
self.viewPurchaseButton.isHidden = true
self.cancelPurchaseButton.isHidden = true
self.refundLoading.alpha = 1
self.refundLoading.startAnimating()
self.getEventDocumentID { (id) in
guard let id = id else { return }
self.makeRefundRequest { [weak self] (done) in
if done == false {
self?.showFailureUI()
} else {
let group = DispatchGroup()
self?.db.collection("student_users/\(user.uid)/events_bought/\(id)/guests").getDocuments { (querySnapshot, error) in
guard error == nil else { return }
guard querySnapshot?.isEmpty == false else { return }
for guest in querySnapshot!.documents {
let name = guest.documentID
let batch = self?.db.batch()
guard let docRef = self?.db.document("student_users/\(user.uid)/events_bought/\(id)/guests/\(name)") else { return }
batch?.deleteDocument(docRef)
group.enter()
batch?.commit { (err) in
guard err == nil else { return }
group.leave()
}
}
}
group.notify(queue: .main) {
DispatchQueue.main.asyncAfter(deadline: .now()+2) {
self?.db.document("student_users/\(user.uid)/events_bought/\(id)").delete { (error) in
guard error == nil else { return }
self?.refundLoading.stopAnimating()
self?.refundLoading.alpha = 0
self?.ticketFormButton.isHidden = false
self?.cancelPurchaseButton.isHidden = true
self?.viewPurchaseButton.isHidden = true
}
}
}
}
}
}
}
alertForCancel.addAction(UIAlertAction(title: "Cancel", style: .cancel))
alertForCancel.addAction(cancelPurchase)
present(alertForCancel, animated: true, completion: nil)
}
当我使用一些断点运行我的应用程序并尝试打印refundMade 的值时,认为这会起作用,
结果是这样的:
我实际上搜索了那个确切的错误,并试图从 StackOverflow 帖子中获取我能找到的东西,但没有任何帮助。这就像我现在的最后一个主要问题,所以如果有人可以提供帮助,将不胜感激,谢谢。
【问题讨论】:
-
也许检查一下这个 Rest API 方法:medium.com/@filswino/…
-
你能在 Swift 中找到一篇文章吗? @Jintor
-
设置断点并检查
done获得的值 - 如果采用if done == false路径,则done必须为 false。你需要弄清楚为什么。同样,在makeRefundRequest中设置断点,看看发生了什么。如果最初的guard失败,您将获得false。您还需要检查您在完成处理程序中对调度组的使用。有些路径您不调用leave,例如guard err == nil else { return }。你也不应该使用try ?- 使用do/try/catch,这样你至少可以打印任何错误。 -
这太奇怪了,因为网络请求每次都成功通过,每次尝试后我都会检查 Stripe,每次都通过 200 OK 退款。还有其他建议吗? @Paulw11
-
refundMade是一个闭包(匿名函数)。Po它没有意义。如果你打印一个函数,你得到的输出就是你所期望的。这不是错误。您需要在闭包内设置断点并单步执行以查看您的代码在做什么。
标签: ios swift google-cloud-firestore grand-central-dispatch dispatch-async