How to handle many API calls with Swift 3 GCD
<p>I am building an swift app to interact with an MDM API to do large numbers of updates via PUT commands, and I am running in to issues with how to handle the massive numbers of API calls without overloading the servers.</p>

<p>I am parsing through a CSV, and each line is an update. If I run the commands asynchronously, it generates and sends ALL of the API calls immediately, which the server doesn't like.</p>

<p>But if I run the commands synchronously, it freezes my GUI which is less than ideal, as the end user doesn't know what's going on, how long is left, if things are failing, etc. </p>

<p>I have also tried creating my own NSOperation queue and setting the max number of items to like 5, and then putting the synchronous function in there, but that doesn't seem to work very well either. It still freezes the GUI with some really random UI updates that seem buggy at best.</p>

<p>The servers can handle 5-10 requests at a time, but these CSV files can be upwards of 5,000 lines sometimes. </p>

<p>So how can I limit the number of simultaneous PUT requests going out in my loop, while not having the GUI freeze on me? To be honest, I don't even really care if the end user can interact with the GUI while it's running, I just want to be able to provide feedback on the lines that have run so far.</p>

<hr>

<p>I have a wrapper which a colleague wrote most of, and the async function looks like this:</p>

<pre><code>func sendRequest(endpoint: String, method: HTTPMethod, base64credentials: String, dataType: DataType, body: Data?, queue: DispatchQueue, handler: @escaping (Response)-&gt;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..&lt;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()
</code></pre>

<p>Then, there is a synchronous option which uses semaphore, which looks like this:</p>

<pre><code>    func sendRequestAndWait(endpoint: String, method: HTTPMethod, base64credentials: String, dataType: DataType, body: Data?) -&gt; 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
}
</code></pre>

<p>Usage information is as follows:</p>

<pre><code>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()

}
</code></pre>

<p>I have modified the send functions slightly, so that they take base64 encoded credentials off the bat, and maybe one or two other things.</p>