Let me introduce you to Swift networking with Siesta — my new favorite library.
Today I’d like to tell you about my new favorite iOS networking library called Siesta. “What’s so great about it, and why can’t I just use Alamofire?” you might ask. Actually, you can use Alamofire with Siesta! Because it’s a networking abstraction layer above HTTP clients.
But unlike libraries like Moya, this one doesn’t hide HTTP from you. This gives you a great middle ground, and that’s exactly how I like to consume my REST APIs.
By adopting a resource-centric approach, rather than a request-centric one, Siesta provides an app-wide observable model of a RESTful resource’s state.
What does this mean? It means avoiding unnecessary network requests and redundant response deserialization. It decouples view controllers from network request lifecycles. It provides transparent response parsing out of the box. And much more.
In this tutorial, I’d like to show you how to get started with this awesomeness and make your networking great again, swiftly 🙌
Install it from Cocoapods:
pod 'Siesta', '~> 1.0'
To get started, create a separate class for your API. Let’s call it
Let’s define a basic API configuration here:
Here we define a global singleton for our API. We configure the service with the URL for our API and
standardTransformers which are default parsers for text and image responses. We also enable logging in debug mode, which is very useful for debugging requests against your API. Finally, we define our first resource accessor, a public method of our API class returning a resource which we are now going to use in our view controller.
To fetch the data from our newly defined resource we need to create a resource observer in our view controller:
Here we add a resource observer to our
ping resource, and define a delegate method which is called when the resource’s state is changed. A state could change when an observer is added or when it has some new data, for example.
Because Siesta allows you to decouple request configuration from request initialization, you can request a resource without worrying about nitty-gritty details of how it would be requested.
For example, you don’t need to worry
loadIfNeeded too often, since Siesta allows you to avoid redundant requests. The default expiration time for a resource is 30 seconds and is configurable.
Now, if you run your app, you should hopefully see something like this:
Siesta:network │ GET https://jwt-api-siesta.herokuapp.com/pingSiesta:network │ Response: 200 ← GET https://jwt-api-siesta.herokuapp.com/pingpong
Let’s do something more fun. Let’s define some transformers which would automatically decode out raw JSON response into a data model.
In our API we have an endpoint
/status which returns
To decode JSON on the backend, we are going to use JSONDecoder, a recent addition to Swift 4.
First, we are going to add a transformer like this:
[String: String] means that we expect a string-to-string mapping dictionary in our JSON response.
Then, we need to update our view controller with resource observer.
As you noticed here, to decode the JSON we are using
typedContent() when unwrapping the optional. In this case, we need to explicitely provide a data type (
[String: String]) otherwise the data type cannot be inferred. Likewise, we can rewrite the previous resource observer for
/ping endpoint like this:
In our API we have a couple of authenticated endpoints:
/expenses. To access them, we’d need to obtain a JWT token first. Let’s define a method to authenticate requests. This time, instead of creating a function that returns a Resource, we are going to make one returning a
Request. This would be a way to handle everything besides GET requests on your API.
First, we are going to add a class property, which would store the JWT authentication token:
Every time this property is set, we want to invalidate our service configuration so that the next time a resouce is fetched, request headers would be refreshed. This is necessary because you’ll likely send your authentication token either in a cookie or in Authorization header.
Also consider storing your authentication token in the Keychain, rather than in
NSUserDefaults or other insecure storage. We are using JWTDecode library here to decode a JWT token and obtain its expiration date.
After that, we also want to automatically refresh the token once it expires. In a more sophisticated implementation of JWT we’d get a refresh token alongside, which we’d later use to refresh our authentication token. In our case, we have a simple JWT implementation, and we are just going to send the login response again.
Here is how you can implement the login request in your AwesomeAPI class to obtain an authentication token:
Here we send a POST to
/login with user credentials in a JSON payload. We also define two closures:
OnFailure and we store an authentication token on a successful authentication.
Finally, we want to automatically update our authentication token before it expires. We can use a single-shot timer for that:
Yes, the actual login credentials for our test API are test and test. You can easily integrate the
AwesomeAPI.login() call in your login flow by obtaining credentials from a view controller responsible for the login. To successfully decode the response from the login request, you need to define a transformer for it as well:
The API requires us to pass the JWT token in the Authorization header. In order to do that, we can add the following to our service configuration (
Now that we have our request authenticated, let’s try to make some requests to authenticated resources, like
/expenses. This endpoint returns a list of the following dictionaries:
Our goal is to create a model to store the response of this format. Let’s create a class called Expense. As we are using JSONDecoder here, we just need to inherit our class from Codable:
The CodingKeys enum allows us to map field names in our JSON response to the struct’s property names. Note that we are also decoding dates here (
createdAt). Since our date has a custom format, we need to configure that via JSONDecoder’s
Finally, let’s create a transformer for this class:
We are using
[Expense] here as we are expecting an array of Expense objects.
After defining an
expenses() resource accessor the same way as we did previously, we can fetch our authenticated resource like this:
One last thing…
One last thing I want to show you is what to do when your authentication token expires. What we could do with Siesta, for example, is automatically authenticate and retry a failed request.
First, we need to add the following to our configuration:
Then we chain our request and repeat it with a new token!
If you want to check out the final project, it’s available on Github.