Skip to content

Instantly share code, notes, and snippets.

@noahbkim
Created July 23, 2018 00:59
Show Gist options
  • Save noahbkim/c090e39ba55bdf99135e05fc91009c60 to your computer and use it in GitHub Desktop.
Save noahbkim/c090e39ba55bdf99135e05fc91009c60 to your computer and use it in GitHub Desktop.
How to make web requests with current swift

For one of the apps I developed I wanted to interface with an API hosted somewhere else on the web. This is the framework I set up to make basic web requests, and it consists of two main components and one optional one. The first handles creating request objects, and uses basic string manipulation along with the URLRequest object from Foundation.

class Http {
    
    public static func join(parameters: [String: String]) -> String {
        if parameters.isEmpty { return "" }
        var items = [String]()
        for (key, value) in parameters { items.append("\(key)=\(value)") }
        return items.joined(separator: "&")
    }
    
    /** Generate a POST request to a URL. */
    public static func post(url: String, parameters: [String: String]=[:]) -> URLRequest {
        var request = URLRequest(url: URL(string: url)!)
        request.httpMethod = "POST"
        request.httpBody = Http.join(parameters: parameters).data(using: .utf8)
        return request
    }
    
    /** Generate a GET request to a URL. */
    public static func get(url: String, parameters: [String: String]=[:]) -> URLRequest {
        let get = Http.join(parameters: parameters)
        var request = URLRequest(url: URL(string: url + "?" + get)!)
        request.httpMethod = "GET"
        return request
    }
    
}

The next component actually handles executing these requests, and relies on the URLSession object. There's a reset function for developer convenience; it closes all requests connected to the session and clears cached data. Depending on the data you're reading, it might invoke the third component, decodeHtmlEntities, which converts things like & to &. I used an implementation of this from Stack Overflow, so I'll leave that to the reader.

public class Requests {
    
    private static var session: URLSession = URLSession(configuration: .default)
    
    /** Reset the session. */
    public class func reset() {
        self.session.finishTasksAndInvalidate()
        self.session = URLSession(configuration: .default)
    }
    
    /** Make an HTTP request expecting a plaintext response. */
    public class func make(
        with request: URLRequest,
        resolve: @escaping (_ string: String, _ response: HTTPURLResponse) -> Void,
        reject: @escaping (_ error: URLError?) -> Void) {
        
        /* Make the data request. */
        self.session.dataTask(with: request, completionHandler: { data, response, error in
            if let response = response as? HTTPURLResponse, let data = data {
                var string = String(data: data, encoding: .utf8)!
                // string = decodeHtmlEntities(string)
                resolve(string, response)
            } else { reject(error) }
        }).resume()
      
    }
    
}

Here's an example of how to use this framework:

// Get https://my.api/endpoint?key=100
let request = Http.get(url: "https://my.api/endpoint", parameters: ["key": 100])
Requests.make(with: request, resolve: { page, response in 
    print(page)  // Raw data from the page
}, reject: { error in print(error) ))

Keep in mind that these requests are asynchronous, so it can be helpful to adopt a promise-like method of yielding values from an API call. For example, if we want to add a wrapper for parsing JSON, we can do the following:

func json(
    with request: URLRequest,
    resolve: @escaping (_ data: Any, _ response: HTTPURLResponse) -> Void,
    reject: @escaping (_ error: URLError) -> Void) {
    
    Requests.make(with: request, resolve: { page, response in
        var json: Any
        let data = page.data(using: .utf8)!
        do { json = try JSONSerialization.jsonObject(with: data) }
        catch {
            reject(.RESPONSE)
            return
        }
        resolve(json, response)
    }, reject: reject)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment