【问题标题】:UI won't update with background API CallUI 不会随着后台 API 调用而更新
【发布时间】: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


【解决方案1】:

这是使用应用程序 PAW 生成的

按照本教程了解如何使用它:https://www.youtube.com/watch?v=44APgBnapag

class MyRequestController {
    func sendRequest() {
        /* Configure session, choose between:
           * defaultSessionConfiguration
           * ephemeralSessionConfiguration
           * backgroundSessionConfigurationWithIdentifier:
         And set session-wide properties, such as: HTTPAdditionalHeaders,
         HTTPCookieAcceptPolicy, requestCachePolicy or timeoutIntervalForRequest.
         */
        let sessionConfig = URLSessionConfiguration.default

        /* Create session, and optionally set a URLSessionDelegate. */
        let session = URLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil)

        /* Create the Request:
           Request (POST https://us-central1-xxxxx-41f12.cloudfunctions.net/createRefund)
         */

        guard var URL = URL(string: "https://us-central1-xxxxx-41f12.cloudfunctions.net/createRefund") else {return}
        var request = URLRequest(url: URL)
        request.httpMethod = "POST"

        // Headers

        request.addValue("application/json", forHTTPHeaderField: "Content-Type")

        // JSON Body

        let bodyObject: [String : Any] = [
            "payment_intent": "id"
        ]
        request.httpBody = try! JSONSerialization.data(withJSONObject: bodyObject, options: [])

        /* Start a new Task */
        let task = session.dataTask(with: request, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) -> Void in
            if (error == nil) {
                // Success
                let statusCode = (response as! HTTPURLResponse).statusCode
                print("URL Session Task Succeeded: HTTP \(statusCode)")
            }
            else {
                // Failure
                print("URL Session Task Failed: %@", error!.localizedDescription);
            }
        })
        task.resume()
        session.finishTasksAndInvalidate()
    }
}

以后

MyRequestController().sendRequest....

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-13
    • 2020-09-10
    • 1970-01-01
    • 2019-03-01
    相关资源
    最近更新 更多