Swift Tip: Networking with Codable
In the very first Swift Talk we built a tiny networking library, leveraging Swift's structs and generics for type-safety and testability. This was in June 2016, and we were still working with Swift 2.2. A lot has changed since then — so let's revisit the code and make use of Swift's latest tricks!
The basic idea was to separate the description of an endpoint from the code executing the network request. The description includes not only the URL, but also the function that can turn data from the network into the desired result:
struct Resource<A> {
let url: URL
let parse: (Data) -> A?
}
The parse function takes Data
and turns it into an optional A
. The advantage of using Data
is that we can deal with any endpoint, irrespective of the data format. But let's face it, more often than not we're dealing with JSON. So we wrote the following initializer to make it easier to create JSON resources:
extension Resource {
init(url: URL, parseJSON: @escaping (Any) -> A?) {
self.url = url
self.parse = { data in
let json = try? JSONSerialization.jsonObject(with: data, options: [])
return json.flatMap(parseJSON)
}
}
}
In the meantime Swift gained the Codable
protocol that makes it super easy to encode and decode values conforming to it. As Aurélien Noce pointed out on Twitter, this makes a very nice addition to this tiny networking library. We can write another initializer on Resource
for types that conform to the Decodable
protocol:
extension Resource where A: Decodable {
init(url: URL) {
self.url = url
self.parse = { try? JSONDecoder().decode(A.self, from: $0) }
}
}
This makes it super easy to create resources for Decodable
types. For example:
struct Episode: Codable {
let id: String
let title: String
}
let allEpisodes = Resouce<[Episode]>(url: url)
Webservice().load(allEpisodes) { print($0) }
Since arrays are also decodable if their elements are, we can create a resource for all episodes — including the function that parses data from the network — in a single line. In fact, we didn't have to write any parsing code ourselves.
Of course this only works if you can determine how your data is being represented in JSON. In real projects you'll often not have this possibility. However, it's still a great technique for quick experiments and prototyping!