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 Starship
?
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 RequestTemplate
.
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 SWAPI
to execute Resource
.
This fetchResource
method looks a lot like what we did with
Now when we run the following code, we’re able to decode our response!
Wrapping up
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 /people
and /starships
) and how we can easily integrate them into a table view.