Skip to content

ralfebert/TinyHTTP

Repository files navigation

TinyHTTP

TinyHTTP is lightweight library for making network calls with URLSession. Its designed to make JSON/REST calls in iOS apps in a controller-owned-networking style easy and straightforward. It comes with an UIKit integration that provides customizable defaults for common tasks like activity indication and error handling out of the box:

It's very lightweight and simple and it will stay that way (less that 1000 lines of code).

Inspired by TinyNetworking and Siesta.

Getting TinyHTTP

TinyHTTP is available via Swift Package Manager: https://github.com/ralfebert/TinyHTTP.git

Example code

The easiest way to try it out is to clone the repository and run the example project from Examples/TodosApp/TodosApp.xcodeproj (please note that the jsonplaceholder.com API that's used for the example doesn't persist your changes).

Guide

Declaring API endpoints

TinyHTTP comes with a type Endpoint for describing a request and how the response is decoded. Let's say you want to make JSON/REST call to your favorite Todos API. Declare a class for the API and declare a method for every endpoint, for example:

struct Todo: Codable {
    var id: Int?
    var title: String?
}

class TodosAPI {

    private let url = URL(string: "https://jsonplaceholder.typicode.com/todos/")!
    
    private let jsonDecoder = JSONDecoder()

    static let shared = TodosAPI()
    private init() {}

    func todos() -> Endpoint<[Todo]> {
        // TinyHTTP provides extensions for common tasks around configuring a URLRequest:
        var request = URLRequest(method: .get, url: self.url)
        request.setHeaderAccept(.json)
        
        // pass in a function to decode the response to the actual type
        // JSONDecoder is extended with a function decodeResponse for easy JSON Codable support:
        return Endpoint(request: request, decodeResponse: self.jsonDecoder.decodeResponse)
    }

}

Making API calls

TinyHTTP provides an extension to URLSession to load an endpoint. HTTP status codes are automatically checked, when the response is available, you get called with a Result value:

let endpoint = TodosAPI.shared.todos()
let task = URLSession.shared.dataTask(endpoint: endpoint) { (result) in
    switch(result) {
    case .success(let todos):
        print("Yay, got todos: ", todos)
    case .failure(let error):
        print("Oh no, an error: ", error)
    }
}
task.resume()

UIKit integration: Loading endpoints in UIViewController classes

To make calls from a UIViewController make the controller conform to the EndpointLoading protocol that extends the class with a load method that loads an endpoint - with a default behavior for activity indication and error handling. For example:

import UIKit
import TinyHTTP

class TodosTableViewController: UITableViewController, EndpointLoading {

    var todos = [Todo]()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.load(endpoint: TodosAPI.shared.todos()) { (todos) in
            self.todos = todos
            self.tableView.reloadData()
        }
    }

    // ...

}

You can configure the app-wide default by setting EndpointDefaults.defaultActivityIndicator and EndpointDefaults.defaultErrorHandler functions or you can customize it per controller via parameters to the call to load.

Using the same endpoint from multiple places in the app

StatefulEndpoint allows to share and observe an endpoint from multiple places in a thread-safe way. You might want to declare it in your API class, f.e.:

class TodosAPI {

    // ...

    lazy var allTodos: StatefulEndpoint<[Todo]> = {
        return StatefulEndpoint(endpoint: self.todos())
    }()

    private func todos() -> Endpoint<[Todo]> {
        var request = URLRequest(method: .get, url: self.url)
        request.setHeaderAccept(.json)
        return Endpoint(request: request, decodeResponse: self.jsonDecoder.decodeResponse)
    }
    
}

You can observe a stateful endpoint instance:

TodosAPI.shared.allTodos.observe(owner: self) { (state) in
    switch(state) {
    case .empty:
        print("not yet")
    case .loading(_):
        print("loading")
    case .success(let todos):
        print("got todos: ", todos)
    case .failure(let error):
        print("oh no: ", error)
    }
}

Or use the EndpointLoading extension which provides an observe(endpoint:) method which again adds default activity and error handling:

class TodosTableViewController: UITableViewController, EndpointLoading {

    // ...
	
    let todosEndpoint = TodosAPI.shared.allTodos

    // ...

    override func viewDidLoad() {
        // ...

        observe(endpoint: self.todosEndpoint) { todos in
            self.todos = todos
            self.tableView.reloadData()
        }

    }

}

Wrapping a full REST API

The example project contains an example for a full typical CRUD REST API, see TodosAPI.swift.

About

TinyHTTP is a lightweight library for making network calls with URLSession.

Resources

License

Stars

Watchers

Forks

Packages

No packages published