与模拟器相比,dispatchgroup 在 testflight 中以不同的顺序执行任务

dispatchgroup executes task in different order in testflight compared to simulator

所以我的目标是在 Xcode 中的 iOS 模拟器和 TestFlight 上的物理设备上都具有一致的功能。所以目前,我有一个功能可以在我的应用程序中处理退款。在模拟器上,该函数按照我期望的顺序运行得很好,但打印语句以错误的顺序执行,我假设这是 TestFlight 模拟行为不当的原因。

方法如下:

@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.makeRefundRequest()
        
       
        DispatchQueue.main.asyncAfter(deadline: .now()+1) {
            let group = DispatchGroup()
            self.db.collection("student_users/\(user.uid)/events_bought/\(nameOfEvent)/guests").getDocuments { (querySnapshot, error) in
                guard error == nil else {
                    print("The guests couldn't be fetched.")
                    return
                }
                guard querySnapshot?.isEmpty == false else {
                    print("The user did not bring any guests.")
                    return
                }
                for guest in querySnapshot!.documents {
                    let name = guest.documentID
                    group.enter()
                    self.db.document("student_users/\(user.uid)/events_bought/\(nameOfEvent)/guests/\(name)").delete { (error) in
                        guard error == nil else {
                            print("The guests couldn't be deleted.")
                            return
                        }
                        print("Guests deleted with purchase refund.")
                        group.leave()
                    }
                }
            }
            group.notify(queue: .main) {
                
                self.db.document("student_users/\(user.uid)/events_bought/\(nameOfEvent)").delete { (error) in
                    guard error == nil else {
                        print("Error trying to delete the purchased event.")
                        return
                    }
                    print("The purchased event was succesfully removed from the database!")
                }
                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)
}

基本上我正在进行的是向 Stripe 发出一个简单的退款请求,然后在我有一个 asyncAfter 代码块并在其中进行了一些数据库清理之后一秒钟。我必须执行 asyncAfter 否则退款请求会被其他异步任务以速度击败。

所以我了解了 DispatchGroups 并决定实施它,因为我在 for 循环中有一个异步任务,我需要在其他任务之前完成它。所以我希望它能正常工作,尽管打印语句的顺序不正确,但是当我通过 TestFlight 运行 我的 phone 上的确切代码块时,我进行了退款并且单元格仍然存在出现在表视图中,意味着文档没有从数据库中正确删除。

我最近在使用 DispatchGroups 和 TestFlight 时遇到了一些可怕的经历,我只是真诚地希望解决所有这些问题并暂时结束这些问题。关于如何修复此方法以防止 TestFlight 上的顺序不正确,有什么建议吗?

更新 决定使用完成处理程序来代替执行相同的功能:

func makeRefundRequest(refundMade: @escaping ((Bool) -> ())) {
    let backendURLForRefund = "https://us-central1-xxxxxx-41f12.cloudfunctions.net/createRefund"
    getStripePaymentIntentID { (paymentid) in
            guard let id = paymentid else { return }
            let url = URL(string: backendURLForRefund)!
            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
                guard let taskError = error?.localizedDescription else { return }
                guard let response = response as? HTTPURLResponse,
                      response.statusCode == 200,
                      let data = data,
                      let _ = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
                    self?.showAlert(title: "Refund Request Error", message: "There was an error making the refund request. \(taskError)")
                    refundMade(false)
                    return
                }
            }
        task.resume()
    refundMade(true)
    }
}

然后我只是在实际的退款处理方法本身中使用了这个方法:

@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.makeRefundRequest { (response) in
            if response == false {
                return
            } else {
                let group = DispatchGroup()
                self.db.collection("student_users/\(user.uid)/events_bought/\(nameOfEvent)/guests").getDocuments { (querySnapshot, error) in
                    guard error == nil else {
                        print("The guests couldn't be fetched.")
                        return
                    }
                    guard querySnapshot?.isEmpty == false else {
                        print("The user did not bring any guests.")
                        return
                    }
                    for guest in querySnapshot!.documents {
                        let name = guest.documentID
                        group.enter()
                        self.db.document("student_users/\(user.uid)/events_bought/\(nameOfEvent)/guests/\(name)").delete { (error) in
                            guard error == nil else {
                                print("The guests couldn't be deleted.")
                                return
                            }
                            print("Guests deleted with purchase refund.")
                            group.leave()
                        }
                    }
                }
                
                group.notify(queue: .main) {
                    
                    self.db.document("student_users/\(user.uid)/events_bought/\(nameOfEvent)").delete { (error) in
                        guard error == nil else {
                            print("Error trying to delete the purchased event.")
                            return
                        }
                        print("The purchased event was succesfully removed from the database!")
                    }
                    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)
}

这实际上不能很好地工作,是的,退款在 Stripe 上进行,数据库被清理了 3 分钟,但是打印语句以错误的顺序打印,并且文档在 3 分钟后神奇地重新出现在 Firestore 数据库中实际看到它被删除了,我该如何防止这种情况并确保它们以正确的顺序打印并以正确的顺序执行以在 TestFlight 上正常工作?这是我的 DispatchGroup 实施中的问题吗?还是完全不同的东西?

我认为重要的是,无论您最终做什么,您都应该学习如何做所有事情。我建议不要在下面使用这种方法,但这是您最初开始的方法,所以不管怎样,让我们​​完成它,这样您就知道调度分组是如何工作的。掌握了这一点后,通过将调度组替换为 Firestore 事务或批处理操作来优化它。事务或批处理操作的要点是所有文档都被自动删除,这意味着它们全部删除或 none 删除。这大大简化了事情,而且它们非常基础!他们的文档非常清楚。

我建议的最后一件事可能是集成一些递归,这意味着如果出现故障,它可以自动重试。递归函数也是非常基础的,所以只需先在 Playground 中学习如何编写一个,然后在这里应用它。一步一个脚印,一两天之内就能搞定。但这是第一步,所以请仔细阅读我写的内容并理解我为什么这样做。

func makeRefundRequest(refundMade: @escaping (_ done: Bool) -> Void) {
    getStripePaymentIntentID { (paymentid) in
        guard let id = paymentid,
              let url = URL(string: "https://us-central1-xxxxxx-41f12.cloudfunctions.net/createRefund") else {
            refundMade(false) // always call completion when you exit this function
            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) // completion: true
            } else {
                if let error = error {
                    print(error)
                }
                refundMade(false) // completion: false
            }
        }

        task.resume()
        // do not call the completion of this function here because you just made a network call
        // so call the completion of this function in that call's completion handler
    }
}

@IBAction func cancelPurchasePressed(_ sender: UIButton) {
    guard let nameOfEvent = selectedEventName,
          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
        // put the UI in a loading state
        self.viewPurchaseButton.isHidden = true
        self.cancelPurchaseButton.isHidden = true
        self.refundLoading.alpha = 1
        self.refundLoading.startAnimating()
        
        self.makeRefundRequest { (done) in
            if done {
                self.db.collection("student_users/\(user.uid)/events_bought/\(nameOfEvent)/guests").getDocuments { (snapshot, error) in
                    guard let snapshot = snapshot,
                          !snapshot.isEmpty else {
                        if let error = error {
                            print(error)
                        }
                        return
                    }
                    let group = DispatchGroup() // instatiate the dispatch group outside the loop

                    for doc in snapshot.documents {
                        group.enter() // enter on each loop iteration
                        let name = doc.documentID

                        self.db.document("student_users/\(user.uid)/events_bought/\(nameOfEvent)/guests/\(name)").delete { (error) in
                            if let error = error {
                                print(error)
                            }
                            group.leave() // leave no matter what the outcome, error or not
                                          // what do you do when this document didn't delete?
                                          // by doing all your deleting in a transaction or batch
                                          // you can ensure that they all delete or none delete
                        }
                    }

                    group.notify(queue: .main) { // done with loop, make final network call
                        self.db.document("student_users/\(user.uid)/events_bought/\(nameOfEvent)").delete { (error) in
                            if let error = error {
                                print(error)
                            }

                            // put the UI back to normal state
                            self.refundLoading.stopAnimating()
                            self.refundLoading.alpha = 0
                            self.ticketFormButton.isHidden = false
                            self.cancelPurchaseButton.isHidden = true
                            self.viewPurchaseButton.isHidden = true
                        }
                    }
                }
            } else { // refund was not made, put the UI back into normal state
                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)
}