如何在后台触发多步骤api事务?

时间:2019-04-17 21:55:05

标签: ios swift

如果有问题,此应用程序为4.2应用程序,但正在升级至5.0,并提供了新功能,包括此功能。

作为对 content-available APN的响应,我需要在将消息发送给第三方之前将本地设备数据与远程数据结合起来。在前台,此过程有效,但在后台,它似乎冻结,直到应用程序在前台。

我想用DispatchQueue解决这个问题-这使我进一步,但是仍然无法解决所有问题。

当我收到我的APN时,请确保它看起来正确(其内容可用通知并具有类别),然后启动 loadPrediction

    // Tells the app that a remote notification arrived that indicates there is data to be fetched.
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler:
        @escaping (UIBackgroundFetchResult) -> Void) {
        guard let aps = userInfo["aps"] as? [String: AnyObject] else {
            completionHandler(.failed)
            return
        }

        if aps["content-available"] as? Int == 1 {
            guard let category = aps["category"] as? String else {
                print("on didReceiveRemoteNotification - did not receive payload with category")
                print(String(describing: userInfo))
                return
            }

            switch category {
            case APNCATEGORIES.PREDICTION.rawValue:
                DataModel.shared.loadPredictions() {
                    completionHandler(.newData)
                }
                break
            default:
                print("on didReceiveRemoteNotification - received unknown category '\(category)'")
                completionHandler(.failed)
            }
        } else  {
            print("on didReceiveRemoteNotification - did not receive content-available in APN")
            print(String(describing: aps))

            completionHandler(.noData)
        }
    }

在loadPredictions中,我从后端请求两个数据。 编辑: I've read that you might want to start a different queue for each POST request,所以我已将下一个代码块修改为当前形式,而不仅仅是一个队列:

    /** load prediction data for notification scheduling */
    func loadPredictions(_ callback: @escaping () -> Void) {
        print("loading predictions")
        let queue = DispatchQueue(label: "loadingPredictions", qos: .utility, attributes: .concurrent)

        queue.sync { [weak self] in
            print("loading predictions - in async task, about to getPredictionsDataFromFirestore")

            self?.getPredictionsDataFromFirestore() { [weak self] in
                print("getting Predictions Data from Firestore")

                if let error = $2 {
                    NotificationCenter.default.post(Notification(name: DataModel.constants.dataFailedToLoad, object: error))
                } else {
                    let apps = $0
                    apps.forEach { app in
                        print("for each app - about to getNotificationSpecificationFromFireStore")
                        let queue = DispatchQueue(label: "getNotificationSpecificationFromFireStore_\(app.name)", qos: .utility, attributes: .concurrent)

                        queue.async { [weak self] in
                            print("getting Notification Specification from FireStore")

                            self?.getNotificationSpecificationFromFireStore(app: app) { [weak self] spec, error in
                                print("got Notification Specification from FireStore, about to post notification")

                                if(error != nil) {
                                    return
                                }
                                guard let spec = spec else {
                                    return
                                }
                                self?.postNotification(app: app, spec: spec)
                            }
                        }
                    }
                    // loadMergedForecasts($1)
                    NotificationCenter.default.post(Notification(name: DataModel.constants.predictionsDataLoaded))
                }

                callback()
            }
        }
    }

它们实际上并不需要像这样相互依赖,但是如果第一个失败,则没有必要做第二个。如果两个都成功,我应该在postNotification中向我的收件人发布通知:

    /** notify third party app of available notificatiions to schedule */
    func postNotification (app: App, spec: NotificationSpecification) {
        print("posting notification")
        do {
            let notify = Data(app.notify.utf8)
            let localNotificationDetails = try JSONDecoder().decode(NotificationDetails.self, from: notify)

            if spec.p8 != "custom" {
                let token = localNotificationDetails.token


            } else {
                guard let bodyJSON = localNotificationDetails.body else {
                    return
                }

                guard let url = spec.custom_endpoint else { return }

                guard let methodString = spec.custom_method?.uppercased() else { return }
                guard let method = HTTPMethod(rawValue:methodString) else { return }
                if ![.post, .put, .patch].contains(method) {
                    print("app has unsupported method '\(method)' -- \(String(describing: app))")
                    return
                }
                guard var headers = spec.custom_headers else { return }
                if !headers.keys.map({ entry_key in entry_key.uppercased() }).contains("CONTENT-TYPE") {
                    headers["Content-Type"] = "application/json"
                }

                print("manually posting the notification with \(String(describing: bodyJSON))")

                let queue = DispatchQueue(label: "manuallyPostNotifications", qos: .utility, attributes: .concurrent)

                AF.request(url, method:method, parameters: bodyJSON).responseJSON(queue: queue) { response in
                    switch response.result {
                    case .success:
                        print("Validation Successful")
                    case .failure(let error):
                        print(error)
                    }
                }
            }
        } catch let e {
            print("error posting notification to app \(app.id)\n\(e)")
        }
    }

视图中没有这些方法。

起初,线索为零,我不知道我是否超过了第一个loadPrediction。在当前状态下,当应用程序处于后台时,日志如下所示:

loading predictions
loading predictions - in async task, about to getPredictionsDataFromFirestore
getting Predictions Data from Firestore
for each app - about to getNotificationSpecificationFromFireStore
getting Notification Specification from FireStore

编辑:这是另外一行,但这并不表示对其他队列的任何改进。

如果我将其前景化,它将完成并成功(当他完全处于前景状态时,整个过程需要1-2秒)。但是我现在想做所有的工作。

问题:

我在排队错了。我如何不耗尽自己正在排队的队列?

任何人都可以确认或否认在关闭应用程序后可以使用吗?我可以看到关闭应用程序后工作已完成,但是此后我再也没有测试过api调用是否有效,因为我无法使其在后台正常工作。

附录

当前答案的修订代码

    /** load prediction data for notification scheduling */
    func loadPredictions(_ callback: @escaping () -> Void) {
        print("loading predictions")
        let queue = DispatchQueue(label: "loadingPredictions", qos: .default)

        queue.sync { [weak self] in
            let group = DispatchGroup()
            group.enter()
            print("loading predictions - in async task, about to getPredictionsDataFromFirestore")

            self?.getPredictionsDataFromFirestore() { [weak self] in
                print("getting Predictions Data from Firestore")

                if let error = $2 {
                    NotificationCenter.default.post(Notification(name: DataModel.constants.dataFailedToLoad, object: error))
                } else {
                    let apps = $0
                    apps.forEach { app in
                        print("for each app - about to getNotificationSpecificationFromFireStore")

                        group.enter()
                        print("getting Notification Specification from FireStore")

                        self?.getNotificationSpecificationFromFireStore(app: app) { [weak self] spec, error in
                            print("got Notification Specification from FireStore, about to post notification")

                            if(error != nil) {
                                group.leave()
                                return
                            }
                            guard let spec = spec else {
                                group.leave()
                                return
                            }
                            self?.postNotification(app: app, spec: spec) {
                                group.leave()
                            }
                        }
                        group.leave()
                    }
                    // loadMergedForecasts($1)
                    NotificationCenter.default.post(Notification(name: DataModel.constants.predictionsDataLoaded))
                    group.leave()
                }
                group.notify(queue: .main) {
                    callback()
                    print("I am being called too early?")
                }
            }
        }
    }

和(在最终方法调用中添加了回调):

    /** notify third party app of available notificatiions to schedule */
    func postNotification (app: App, spec: NotificationSpecification, _ callback: @escaping () -> Void ) {
        print("posting notification")
        do {
            let notify = Data(app.notify.utf8)
            let localNotificationDetails = try JSONDecoder().decode(NotificationDetails.self, from: notify)

            if spec.p8 != "custom" {
                let token = localNotificationDetails.token

                callback()
            } else {
                guard let bodyJSON = localNotificationDetails.body else {
                    callback()
                    return
                }

                guard let url = spec.custom_endpoint else {
                    callback()
                    return
                }

                guard let methodString = spec.custom_method?.uppercased() else {
                    callback()
                    return
                }
                guard let method = HTTPMethod(rawValue:methodString) else {
                    callback()
                    return
                }
                if ![.post, .put, .patch].contains(method) {
                    print("app has unsupported method '\(method)' -- \(String(describing: app))")
                    callback()
                    return
                }
                guard var headers = spec.custom_headers else { return }
                if !headers.keys.map({ entry_key in entry_key.uppercased() }).contains("CONTENT-TYPE") {
                    headers["Content-Type"] = "application/json"
                }

                print("manually posting the notification with \(String(describing: bodyJSON))")

                let queue = DispatchQueue(label: "manuallyPostNotifications", qos: .utility, attributes: .concurrent)

                AF.request(url, method:method, parameters: bodyJSON).responseJSON(queue: queue) { response in
                    switch response.result {
                    case .success:
                        print("Validation Successful")
                    case .failure(let error):
                        print(error)
                    }

                    callback()
                }
            }
        } catch let e {
            print("error posting notification to app \(app.id)\n\(e)")
            callback()
        }
    }

意识到我的打印语句不在notify回调内,所以我对其进行了修改-仍未进入第二个Firebase调用。

loading predictions
loading predictions - in async task, about to getPredictionsDataFromFirestore
getting Predictions Data from Firestore
for each app - about to getNotificationSpecificationFromFireStore
getting Notification Specification from FireStore
I am being called too early?

1 个答案:

答案 0 :(得分:1)

您正在解雇异步任务,并且在完成这些任务之前将执行callback()。由于callback()最终会调用completionHandler,因此您的应用将在所有工作完成之前被暂停。

您可以使用调度组将callBack()延迟到完成所有操作。不需要额外的调度队列。

func loadPredictions(_ callback: @escaping () -> Void) {
    print("loading predictions")

    let dispatchGroup = DispatchGroup() 
    print("loading predictions - in async task, about to getPredictionsDataFromFirestore")

    dispatchGroup.enter() 

    self.getPredictionsDataFromFirestore() {
        print("getting Predictions Data from Firestore")
        if let error = $2 {
            NotificationCenter.default.post(Notification(name: DataModel.constants.dataFailedToLoad, object: error))
        } else {
            let apps = $0
            apps.forEach { app in
                print("for each app - about to getNotificationSpecificationFromFireStore")
                dispatchGroup.enter()
                self.getNotificationSpecificationFromFireStore(app: app) { spec, error in
                    print("got Notification Specification from FireStore, about to post notification")
                    if(error != nil) {
                        dispatchGroup.leave()
                        return
                    }
                    guard let spec = spec else {
                        dispatchGroup.leave()
                        return
                    }
                    self.postNotification(app: app, spec: spec)
                    dispatchGroup.leave()
                }
            }
        }
        NotificationCenter.default.post(Notification(name: DataModel.constants.predictionsDataLoaded))
        dispatchGroup.leave()
    }
    dispatchGroup.notify(queue: .main) {
        callback()
    }
}