如何使用Swift 3 GCD处理许多API调用

时间:2016-11-16 05:19:11

标签: swift api csv swift3 grand-central-dispatch

我正在构建一个与MDM API交互的swift应用程序,通过PUT命令进行大量更新,我正在讨论如何处理大量API调用而不会使服务器过载的问题。

我正在解析CSV,每行都是更新。如果我以异步方式运行命令,它会立即生成并发送所有API调用,这是服务器不喜欢的。

但是,如果我同步运行命令,它会冻结我的GUI,这不太理想,因为最终用户不知道发生了什么,剩下多长时间,如果事情失败等等。

我还尝试创建自己的NSOperation队列并将最大项目数设置为5,然后将同步函数放在那里,但这似乎也不能很好地工作。它仍然会冻结GUI,其中包含一些非常随机的UI更新,这些更新最多只会出现问题。

服务器一次可以处理5-10个请求,但有时这些CSV文件可能超过5,000行。

那么如何在我的循环中限制同时发送的PUT请求的数量,同时不冻结GUI呢?说实话,我甚至不关心最终用户是否可以在GUI运行时与GUI进行交互,我只是希望能够提供有关到目前为止运行的行的反馈。

我有一个同事写的大部分包装器,async函数看起来像这样:

func sendRequest(endpoint: String, method: HTTPMethod, base64credentials: String, dataType: DataType, body: Data?, queue: DispatchQueue, handler: @escaping (Response)->Swift.Void) {
    let url = self.resourceURL.appendingPathComponent(endpoint)
    var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30.0)
    request.httpMethod = "\(method)"

    var headers = ["Authorization": "Basic \(base64credentials)"]
    switch dataType {
    case .json:
        headers["Content-Type"] = "application/json"
        headers["Accept"] = "application/json"
        if let obj = body {
            do {
                request.httpBody = try JSONSerialization.data(withJSONObject: obj, options: JSONSerialization.WritingOptions(rawValue: 0))
            } catch {
                queue.async {
                    handler(.badRequest)
                }
                return
            }
        }
    case .xml:
        headers["Content-Type"] = "application/xml"
        headers["Accept"] = "application/xml"
        request.httpBody = body
        /*if let obj = body {
            request.httpBody = (obj as! XMLDocument).xmlData
        }*/
    }
    request.allHTTPHeaderFields = headers

    session.dataTask(with: request) {
        var response: Response
        if let error = $2 {
            response = .error(error)
        } else {
            let httpResponse = $1 as! HTTPURLResponse
            switch httpResponse.statusCode {
            case 200..<299:
                if let object = try? JSONSerialization.jsonObject(with: $0!, options: JSONSerialization.ReadingOptions(rawValue: 0)) {
                    response = .json(object)
                } else if let object = try? XMLDocument(data: $0!, options: 0) {
                    response = .xml(object)
                } else {
                    response = .success
                }
            default:
                response = .httpCode(httpResponse.statusCode)
            }
        }

        queue.async {
            handler(response)
        }
        }.resume()

然后,有一个使用信号量的同步选项,如下所示:

    func sendRequestAndWait(endpoint: String, method: HTTPMethod, base64credentials: String, dataType: DataType, body: Data?) -> Response {
    var response: Response!
    let semephore = DispatchSemaphore(value: 0)
    sendRequest(endpoint: endpoint, method: method, base64credentials: base64credentials, dataType: dataType, body: body, queue: DispatchQueue.global(qos: .default)) {
        response = $0
        semephore.signal()
    }
    semephore.wait()
    return response
}

使用信息如下:

class ViewController: NSViewController {

let client = JSSClient(urlString: "https://my.mdm.server:8443/", allowUntrusted: true)
let credentials = JSSClient.Credentials(username: "admin", password: "ObviouslyNotReal")


func asynchronousRequestExample() {
    print("Sending asynchronous request")

    client.sendRequest(endpoint: "computers", method: .get, credentials: credentials, dataType: .xml, body: nil, queue: DispatchQueue.main) { (response) in

        print("Response recieved")

        switch response {
        case .badRequest:
            print("Bad request")
        case .error(let error):
            print("Receieved error:\n\(error)")
        case .httpCode(let code):
            print("Request failed with http status code \(code)")
        case .json(let json):
            print("Received JSON response:\n\(json)")
        case .success:
            print("Success with empty response")
        case .xml(let xml):
            print("Received XML response:\n\(xml.xmlString(withOptions: Int(XMLNode.Options.nodePrettyPrint.rawValue)))")
        }

        print("Completed")
    }

    print("Request sent")

}

func synchronousRequestExample() {
    print("Sending synchronous request")

    let response = client.sendRequestAndWait(endpoint:  "computers", method: .get,credentials: credentials, dataType: .json, body: nil)

    print("Response recieved")

    switch response {
    case .badRequest:
        print("Bad request")
    case .error(let error):
        print("Receieved error:\n\(error)")
    case .httpCode(let code):
        print("Request failed with http status code \(code)")
    case .json(let json):
        print("Received JSON response:\n\(json)")
    case .success:
        print("Success with empty response")
    case .xml(let xml):
        print("Received XML response:\n\(xml.xmlString(withOptions: Int(XMLNode.Options.nodePrettyPrint.rawValue)))")
    }

    print("Completed")
}


override func viewDidAppear() {
    super.viewDidAppear()
    synchronousRequestExample()
    asynchronousRequestExample()

}

我稍微修改了发送功能,这样他们就可以使用base64编码的凭证,也许还有一两件其他的东西。

2 个答案:

答案 0 :(得分:0)

您是否只是将操作链接到每次操作一次发送3/4请求?

https://www.raywenderlich.com/76341/use-nsoperation-nsoperationqueue-swift

您知道,NSOperation(也通过Operation with Swift3提取)默认运行在后台线程上。请注意不要在完成块中运行可能在主线程上运行任务的繁重任务(这将冻结您的UI)。

我看到的唯一可以冻结UI的其他情况是一次执行太多操作。

答案 1 :(得分:0)

嗯,我想我已经覆盖了这个!我决定爬出兔子洞,简化一些事情。我编写了自己的会话,而不是依赖于包装器,并在其中设置信号量,将其放在OperationQueue中,它似乎工作得很好。

这是我设置简化信号量请求的视频。 https://www.youtube.com/watch?v=j4k8sN8WdaM

我必须将以下代码调整为PUT而不是我用于测试的GET,但这部分很容易。

//print (row[0])
    let myOpQueue = OperationQueue()
    myOpQueue.maxConcurrentOperationCount = 3
    let semaphore = DispatchSemaphore(value: 0)
    var i = 0
    while i < 10 {
        let myURL = NSURL(string: "https://my.server.com/APIResources/computers/id/\(i)")
        myOpQueue.addOperation {

            let request = NSMutableURLRequest(url: myURL! as URL)
            request.httpMethod = "GET"
            let configuration = URLSessionConfiguration.default
            configuration.httpAdditionalHeaders = ["Authorization" : "Basic 123456789ABCDEFG=", "Content-Type" : "text/xml", "Accept" : "text/xml"]
            let session = Foundation.URLSession(configuration: configuration)
            let task = session.dataTask(with: request as URLRequest, completionHandler: {
                (data, response, error) -> Void in
                if let httpResponse = response as? HTTPURLResponse {
                    print(httpResponse.statusCode)
                    semaphore.signal()
                    self.lblLine.stringValue = "\(i)"
                    self.appendLogString(stringToAppend: "\(httpResponse.statusCode)")
                    print(myURL!)

                }
                if error == nil {
                    print("No Errors")
                    print("")
                } else {
                    print(error!)
                }
            })

            task.resume()
            semaphore.wait()

        }
        i += 1
    }