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

How to handle many API calls with Swift 3 GCD

我正在构建一个 swift 应用程序来与 MDM API 交互以通过 PUT 命令进行大量更新,我正在 运行 解决如何在不使服务器超载的情况下处理大量 API 调用。

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

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

我也试过创建自己的 NSOperation 队列并将最大项目数设置为 5,然后将同步函数放在那里,但这似乎也不是很好。它仍然冻结了 GUI 一些非常随机的 UI 更新,这些更新充其量似乎是错误的。

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

那么我如何限制循环中同时发出的 PUT 请求的数量,同时又不让 GUI 冻结?老实说,我什至不在乎最终用户是否可以在 运行ning 时与 GUI 进行交互,我只是希望能够在具有 [=] 的行上提供反馈41=] 到目前为止。


我有一个同事写的大部分包装器,异步函数如下所示:

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 =  {
            response = .error(error)
        } else {
            let httpResponse =  as! HTTPURLResponse
            switch httpResponse.statusCode {
            case 200..<299:
                if let object = try? JSONSerialization.jsonObject(with: [=12=]!, options: JSONSerialization.ReadingOptions(rawValue: 0)) {
                    response = .json(object)
                } else if let object = try? XMLDocument(data: [=12=]!, 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 = [=13=]
        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 编码的凭据,也许还有一两个其他的东西。

你不能只是链式操作,每次操作一次发送 3/4 个请求吗?

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

如你所知,NSOperation(也由 Operation with Swift3 抽象)默认在后台线程上 运行ning。请注意不要在您的完成块中执行 运行 繁重的任务,这些任务可能会在主线程上执行 运行 任务(这会冻结您的 UI)。

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

好吧,我想我知道了!我决定以某种方式爬出兔子洞并简化事情。我编写了自己的会话而不是依赖包装器,并在其中设置了信号量,将其放入 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
    }