【问题标题】:Swift - multiple Chain http request with loopSwift - 带循环的多链 http 请求
【发布时间】:2026-01-03 03:45:01
【问题描述】:

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

  1. 上传图片到服务器

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

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

我最后一次尝试是使用PromiseKit 以干净的方式链接请求,如本文所述:Chain multiple Alamofire requests。但是如果状态尚未完成,我不知道如何每 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
}

【问题讨论】:

    标签: ios swift alamofire promisekit


    【解决方案1】:

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

    /// 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)")
    }
    

    以上几点注意事项:

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

    • 我显然没有进行任何 XML 解析。但是您显然会解析响应并相应地确定taskCompleted

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

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

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

    【讨论】:

    • 谢谢。这是我的案例的完美示例。现在我可以清理我的解决方法以取回干净的代码!
    【解决方案2】:

    查看没有 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 ?? "")")
        }
    }
    

    【讨论】:

      最近更新 更多