2

从 2 天开始,我感觉我正在搜索整个网络来解决我的多个 http 请求问题。所以我的工作流程是这样的:

  1. 将图像上传到服务器

    • 响应 = 带有任务 ID 的 XML 格式
  2. 使用任务 ID 向服务器发出 GET 请求以检查此任务的状态。

    • 响应 = XML 格式,其中状态可能是“已完成”、“进行中”、“已排队”
    • 如果 Status != "Completed" - 重试第 2 步
    • 如果状态 == “已完成” - 转到第 3 步
  3. 从 resultUrl 下载结果

我最后一次尝试是用PromiseKit一种干净的方式来链接请求,就像这篇文章中描述的那样:链接多个 Alamofire 请求。但如果状态尚未完成,我不知道如何每 2-5 秒循环第二步。

此工作流程是否有推荐的解决方案?这是我的测试PromiseKit,我成功地链接了没有循环的请求:

let request = Client.imageUploadRequest(image: imageView.image!)
let httpOperation = HTTPOperation(withRequest: request)

httpOperation.sendRequest().then() { result -> Promise<String> in
    let xml = SWXMLHash.parse(result)
    let id = self.getXMLAttribute(from: xml, with: "id")!
    let taskStatusrequest =  Client.getTaskStatusRequest(withTaskID: id)
    let httpOperation = HTTPOperation(withRequest: taskStatusrequest)

    return httpOperation.sendRequest()
}
// Loop this result if status != "Completed"
.then { result -> Promise<Data> in
    let xml = SWXMLHash.parse(result)
    let downloadUrl = self.getXMLAttribute(from: xml, with: "resultUrl")!
    let downloadRequest = Client.getDownloadRequest(withUrl: downloadUrl)
    let httpOperation = HTTPOperation(withRequest: downloadRequest)

    // if status != "Completed" don't return, retry this step
    return httpOperation.downloadData()
}
.then { _ -> Void in
    // Refresh View with data
}
4

2 回答 2

2

基本思想是编写一个例程来重试相关请求,并且仅在满足某些条件时才履行承诺:

/// Attempt a network request.
///
/// - Parameters:
///   - request:    The request.
///   - maxRetries: The maximum number of attempts to retry (defaults to 100).
///   - attempt:    The current attempt number. You do not need to supply this when you call this, as this defaults to zero.
///   - fulfill:    The `fulfill` closure of the `Promise`.
///   - reject:     The `reject` closure of the `Promise.

private func retry(_ request: URLRequest, maxRetries: Int = 100, attempt: Int = 0, fulfill: @escaping (Data) -> Void, reject: @escaping (Error) -> Void) {
    guard attempt < maxRetries else {
        reject(RetryError.tooManyRetries)
        return
    }

    Alamofire.request(request)
        .validate()
        .responseData { response in
            switch response.result {
            case .success(let value):
                let taskCompleted = ...          // determine however appropriate for your app
                let serverReportedFailure = ...

                if serverReportedFailure {
                    reject(RetryError.taskFailed)
                } else if taskCompleted {
                    fulfill(value)
                } else {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
                        self.retry(request, maxRetries: maxRetries, attempt: attempt + 1, fulfill: fulfill, reject: reject)
                    }
                }
            case .failure(let error):
                reject(error)
            }
    }
}

/// Error codes for retrying of network requests.

enum RetryError: Error {
    case tooManyRetries
    case taskFailed
}

然后,您可以使用一种方法来创建上述满足的承诺:

/// Create a promise for a network request that will be retried until
/// some criteria is met.
///
/// - Parameter request: The request to be attempted.
/// - Returns: The `Promise`.

private func retry(for request: URLRequest) -> Promise<Data> {
    return Promise { fulfill, reject in
        self.retry(request, fulfill: fulfill, reject: reject)
    }
}

您现在可以Promise使用上述内容执行标准操作,例如:

retry(for: request).then { data in
    print("received \(data)")
}.catch { error in
    print("error: \(error)")
}

上面的一些警告:

  • 我打电话fulfillData. 通常你会有一些模型对象或类似的东西,但我不确定在你的情况下什么是合适的。

  • 我显然没有做任何 XML 解析。但是您显然会解析响应并做出taskCompleted相应的决定。

  • 尽管您说任务的状态可能是“排队”、“进行中”和“已完成”,但我认为如果排队任务失败,您最终会让服务器进程添加一些错误处理。所以,我还添加了一个taskFailed错误。做你认为合适的事。

  • 我对最大重试次数以及如果出现网络错误该怎么办做了一些假设。显然,根据您的情况自定义这些条件。

因此,不要迷失在我的示例的细节中,而是关注更广泛的情况,即您可以有一个例程来创建 aPromise并将两个fulfillreject闭包传递给一个单独的例程,该例程实际执行重试逻辑。

于 2016-11-20T07:53:22.717 回答
0

请参阅在没有 Alamofire 的情况下链接 https 请求:POST+GET+GET+GET...

class ViewController1: UIViewController, URLSessionDataDelegate {
    var URLSessionConfig :URLSessionConfiguration!
    var session: URLSession?
    var task0: URLSessionTask!
    var task1: URLSessionTask!
    var task2: URLSessionTask!
    var task3: URLSessionTask!

    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        self.URLSessionConfig = URLSessionConfiguration.ephemeral
        #if available
        self.URLSessionConfig.waitsForConnectivity = true
        #endif
        self.session = URLSession(configuration: URLSessionConfig, delegate: self, delegateQueue: OperationQueue.main)
    }

    func Start() {
        let url0 = URL(string: "https://myserver/PRIMARY_PATH/")!
        var req0 = URLRequest(url: url0)
        req0.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
        req0.httpMethod = "POST"
        req0.httpBody = str.data(using: .utf8)
        self.task0 = self.session?.dataTask(with: req0 as URLRequest)
        self.task0.resume()
    }

    func parseData0(didReceive data: Data) -> URLRequest? {
        do {
            let json = try JSONSerialization.jsonObject(with: data) as? [String: String]
            ...
            let url1 = URL(string: "https://myserver/SECONDARY_PATH/"+...)!
            var req1 = URLRequest(url: url1)
            req1.httpMethod = "GET"            
            return req1
        }
        catch let parseError {
            debugPrint("parsing error: \(parseError)")
            return nil
        }
    }
    func parseData1(didReceive data: Data) -> URLRequest? {
        do {
            let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
            ...
        }
        catch let parseError {
            debugPrint("parsing error: \(parseError)")
            return nil
        }
    }

    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        debugPrint("Data received: \(data)")
        if dataTask == self.task0 {
            let req1: URLRequest? = parseData0(didReceive: data)
            if req1 != nil {
                DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                    self.task1 = self.session?.dataTask(with: req1!)
                    self.task1.resume()
                }
            }
        }
        if dataTask == self.task1 {
            let req1: URLRequest? = parseData1(didReceive: data)
            if req1 != nil {
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
                    self.task2 = self.session?.dataTask(with: req1!)
                    self.task2.resume()
                }
            }
        }
        if dataTask == self.task2 {
            let req1: URLRequest? = parseData1(didReceive: data)
            if req1 != nil {
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
                    self.task3 = self.session?.dataTask(with: req1!)
                    self.task3.resume()
                }
            }
        }
        if dataTask == self.task3 {
            let req1: URLRequest? = parseData1(didReceive: data)
            if req1 != nil {
                ...
            }
        }
    }

    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {        
        debugPrint("Response received: \(response)")
        completionHandler(URLSession.ResponseDisposition.allow)
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if error != nil {
            debugPrint("error message: \(error!)")
            debugPrint("code: \(error!._code)")
        }
    }

    func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
        debugPrint("there was an error: \(error?.localizedDescription ?? "")")
    }
}
于 2018-11-20T22:52:59.390 回答