我正在构建一个与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编码的凭证,也许还有一两件其他的东西。
答案 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
}