Last time, we looked at how to define the requests in our network layer in a way that keeps our code clean.
But we only looked at one side of the equation because all we were doing was fetching data from the server. It’s a good start, but we’ll probably need to decode it before we can use it in our app.
Will we be able to continue to use this DRY compositional approach in order to handle responses as well?
(Spoiler alert: Yes)
Today, we’ll have a look at:
- How to match a request to a response.
- How to use composition in order to build higher-level objects.
- How we can optimize our code to make the most common path easy.
Let’s get to it!
Matching requests to response with the Resource type
It’s nice to know we’re able to fetch
Data from our server. But now we need to couple these requests with a transformation to Data to the right type. Let’s refresh our memory with a new request that fetches a
Person with a given ID:
Last time, we took a step back to think about how we could represent a request. Let’s do the same with a response. What does it mean for us to handle a response?
We’re going to receive some
Data, and we need to transform it into a type that the application can use. Often, it’s going to be a model that we define, but let’s not limit ourselves to that. Maybe you’ll want to do something different, like save the contents of the response to Core Data so they can be fetched later.
I think the best way to represent this type of transformation is with a closure from Data to the type that we want.
So let’s see this in action. Let’s take the response from
GetPersonRequet and parse them into a
Person struct. Lucky for us, Apple provides the
Codable protocol to make this easy.
Now we need to figure out how exactly do we get data from our request parsed into this struct? I propose creating a
Resource type that will encompass both the request with and a closure to transform it into what we need.
Here’s how that would look for fetching People:
Not too bad, right? Note that we marked the closure with
throws, since we’ll be using
JsonDecoder and we’ll want to surface its errors. All sorts of errors can happen at this level of our application, so it’s important we make them visible ☝️
Now that we’ve defined a resource for
Person, how would this look for
Your “Don’t Repeat Yourself” alarm from last time should already be sounding off. Let’s generalize this concept to something we can use with any
Alright. Now we have something we can work with! Instead of creating a struct for every
Resource pair we can think of, instead we’ve used a generic type to represent the type we want to decode.
I also want to point out an important design decision here: we’re not coupling the request to a given type of response. We could parse the same request in multiple different ways if we wanted to. I find this to be a cool feature to have available for more complex applications.
Now let’s take this
Resource struct for a spin!
Our first resource
Creating a resource should be fairly straightforward. Let’s see what decoding
/people/:id would look like using its default initializer:
I can imagine quite a few of our resources (like getting a starship) would look like this, don’t you think? Let’s see if we can define an initializer that would make this creating a bit more straightforward.
This makes things much nicer for ourselves. Our code from above can now become:
Finally, much like we did in the last article with
RequestTemplate, we can define a new method on
fetchResource method looks a lot like what we did with
Now when we run the following code, we’re able to decode our response!
How many times have you run into libraries that were supposed to make your life easier, but didn’t accommodate your specific use case? Hopefully, we’ve built something here that can do both!
Here’s what I consider the key things to remember:
- We used small building blocks (RequestTemplate) to create bigger blocks (Resources). This gives us confidence in the abstraction that we built.
- Creating a new resource is simple and succinct. How to create a new resource is clear and concise.
- We’ve made the common case easy, while remaining flexible enough for cases that would require custom parsing.
Next time, we’ll look into how to parse paged resources (like
/starships) and how we can easily integrate them into a table view.