Foreword
I decided to build a small reference of useful Swift snippets I’m calling Survival Swift. Common stuff we do in many projects.
Rather than Googling I figured I’ll save some time by hosting the answers myself with some home grown examples of my own. I’ll eventually build these into a separate page but for now they will make nice micro posts.
I’ll keep the copy short and let the code do the talking since most will probably skip to that part anyway.
Network Fetch in Swift
Here’s a small example of the most basic—and generic (<T>
)—way to handle
a simple network get request in Swift/Foundation.
The API
We’ll use a sample REST API I found online: https://dog.ceo/api/breeds/image/random – it returns random dog photos :)
How to call it
First I’ll show how to use the sample code, then the full code for the networking.
Using the generic method directly
Using the generic method directly requires us to type annotate our closure parameters.
// Using explicit types in closure since the network client is using generic types
// this is the only way the compiler can infer the Decodable type since calling
// fetch<DogPhoto>(urlString:) is strictly prohibited
NetworkClient.fetch(urlString: "https://dog.ceo/api/breeds/image/random") { (dogPhoto: DogPhoto?, error: Error?) -> Void in
print(">> Dog Photo: \(dogPhoto)")
}
If you want to avoid that then make a separate client…
Using in a custom network client
Or we can call our generic function from our own custom function and get the type inference to do that for us:
enum DogPhotoClient {
static let dogUrl = "https://dog.ceo/api/breeds/image/random"
/// Requests random dog photos from dog photo service
static func fetchDogPhoto(completion: @escaping (DogPhoto?, Error?) -> Void) {
NetworkClient.fetch(
urlString: Self.dogUrl,
completion: completion)
}
}
Since our completion parameter specifies the types we can just pass the
completion closure directly to our underlying fetch
call and get free
type inference.
The Codable DogPhoto response struct
Here we use the additional optional CodingKeys
sub enum to rename some of the
api’s JSON keys to a more suitable semantic name.
Note: if we only want to use
this for decoding we could conform to the Decodable
protocol and likewise
the Encodable
protocol if we only need to encode.
Read more on Codable at Apple.com.
A sample response looks like this:
{
"message": "https://images.dog.ceo/breeds/wolfhound-irish/n02090721_2116.jpg",
"status": "success"
}
Let’s model a struct after the response.
/// Represents a DogPhoto api response
struct DogPhoto: Codable {
let status: String
let photoUrl: String
/// Custom encoding/decoding keys (optional).
/// Used to rename the api's response keys to our own internal names.
enum CodingKeys: String, CodingKey {
/// maps the `message` key in the json to the `photoUrl`
/// field of our response struct
case photoUrl = "message"
/// since CodingKeys must be exhaustive, we provide status with no override
case status
}
}
The Network client
This code does the heavy lifting
enum NetworkClient {
/// Reusable URLSession for making requests
private static let session: URLSession = {
URLSession(configuration: .default)
}()
typealias Response<T: Decodable> = (T?, Error?) -> Void
/// fetches any kind of `Decodable` object from given url string
static func fetch<T: Decodable>(urlString: String, completion: @escaping Response<T>) {
guard let url = URL(string: urlString) else {
completion(nil, Err.badUrl(urlString))
return
}
session.dataTask(with: url) { data, response, error in
guard let data = data else {
completion(nil, error ?? Err.noData)
return
}
guard let decoded: T = decode(data: data) else {
completion(nil, Err.decodeFailed)
return
}
completion(decoded, nil)
}
// must call resume on the task to fire off the network request
.resume()
}
/// decodes response data into a `Decodable` object
private static func decode<T: Decodable>(data: Data) -> T? {
do {
return try JSONDecoder().decode(T.self, from: data)
}
catch {
// the real error handling is done by our caller (fetch)
print(error)
}
return nil
}
/// Various errors to return from our service
enum Err: Error {
/// not a valid URL
case badUrl(_: String)
/// no data was returned
case noData
///
case decodeFailed
}
}