Swift - 带循环的多链 http 请求
Swift - multiple Chain http request with loop
2 天以来,我感觉我正在搜索整个网络来解决我的多个 http 请求问题。所以我的工作流程是这样的:
上传图片到服务器
- 响应 = XML 带有任务 ID 的格式
使用任务 ID 向服务器发出 GET 请求以检查此任务的状态。
- 响应 = XML 状态可以是 "Completed"、"In Progress"、"Queued"
的格式
- 如果状态 != "Completed" - 重试步骤 2
- 如果状态 == "Completed" - 转到步骤 3
从resultUrl下载结果
我最后一次尝试是使用 PromiseKit
以一种干净的方式链接请求,如 post 中所述:。但是如果状态还没有完成,我不知道如何每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
}
基本思想是编写一个重试相关请求的例程,并且仅在满足特定条件时才履行承诺:
/// 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)")
}
以上几点注意事项:
我正在用 Data
呼叫 fulfill
。通常你会有一些模型对象或类似的东西,但我不确定什么适合你的情况。
我显然没有做任何 XML 解析。但是您显然会解析响应并相应地确定 taskCompleted
。
尽管您说任务的状态可能是 "queued"、"in progress" 和 "completed",但我假设您最终会拥有服务器的进程如果排队任务失败,添加一些错误处理。因此,我还添加了一个 taskFailed
错误。随心所欲。
我对最大重试次数以及出现网络错误时的处理方式做了一些假设。显然,根据您的情况自定义这些条件。
所以,不要迷失在我的示例的细节中,而是关注更广泛的画面,即您可以创建一个例程来创建 Promise
并传递两个 fulfill
和 reject
关闭实际执行重试逻辑的单独例程。
查看不使用 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 ?? "")")
}
}
2 天以来,我感觉我正在搜索整个网络来解决我的多个 http 请求问题。所以我的工作流程是这样的:
上传图片到服务器
- 响应 = XML 带有任务 ID 的格式
使用任务 ID 向服务器发出 GET 请求以检查此任务的状态。
- 响应 = XML 状态可以是 "Completed"、"In Progress"、"Queued" 的格式
- 如果状态 != "Completed" - 重试步骤 2
- 如果状态 == "Completed" - 转到步骤 3
从resultUrl下载结果
我最后一次尝试是使用 PromiseKit
以一种干净的方式链接请求,如 post 中所述:
这个工作流有推荐的解决方案吗?这是我使用 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
}
基本思想是编写一个重试相关请求的例程,并且仅在满足特定条件时才履行承诺:
/// 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)")
}
以上几点注意事项:
我正在用
Data
呼叫fulfill
。通常你会有一些模型对象或类似的东西,但我不确定什么适合你的情况。我显然没有做任何 XML 解析。但是您显然会解析响应并相应地确定
taskCompleted
。尽管您说任务的状态可能是 "queued"、"in progress" 和 "completed",但我假设您最终会拥有服务器的进程如果排队任务失败,添加一些错误处理。因此,我还添加了一个
taskFailed
错误。随心所欲。我对最大重试次数以及出现网络错误时的处理方式做了一些假设。显然,根据您的情况自定义这些条件。
所以,不要迷失在我的示例的细节中,而是关注更广泛的画面,即您可以创建一个例程来创建 Promise
并传递两个 fulfill
和 reject
关闭实际执行重试逻辑的单独例程。
查看不使用 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 ?? "")")
}
}