iOS Networking
Here the snippets I keep reusing when I build iOS apps that need to communicate with a HTTP/JSON api.
Endpoint
This snippet aims to generalize an HTTP endpoint.
protocol Endpoint {
var base: String { get }
var path: String { get }
var method: HTTPMethod { get }
var headers: [String: String] { get }
var body: Encodable { get }
var queryParameters: [URLQueryItem] { get }
}
extension Endpoint {
private var url: URL {
var components = URLComponents()
components.scheme = "https"
components.host = base
components.path = path
components.queryItems = queryParameters
guard let url = components.url else {
preconditionFailure("Invalid URL components: \(components)")
}
return url
}
var urlRequest: URLRequest {
var request = URLRequest(url: self.url)
request.httpMethod = self.method.rawValue
self.headers.forEach { (k, v) in
request.setValue(v, forHTTPHeaderField: k)
}
if self.method == HTTPMethod.post || self.method == .put {
do {
let encoder = JSONEncoder()
request.httpBody = try encoder.encode(self.body)
} catch {
print(error.localizedDescription)
}
}
return request
}
}
enum HTTPMethod: String {
case post = "POST"
case get = "GET"
case put = "PUT"
case patch = "PATCH"
case delete = "DELETE"
case head = "HEAD"
case options = "OPTIONS"
}
The following snippet performs the actual HTTP requests for a given endpoint:
enum NetworkingError: Error {
case networkError
case decodingError
case notfoundError
case apiError
}
@MainActor
struct Networking {
func performRequest<T: Decodable>(endpoint: Endpoint, decoder: JSONDecoder = JSONDecoder()) async -> Result<T, NetworkingError> {
do {
let (data, response) = try await URLSession.shared.data(for: endpoint.urlRequest)
guard let httpResponse = response as? HTTPURLResponse else {
return .failure(.networkError)
}
if !(200...299).contains(httpResponse.statusCode) {
return .failure(.apiError)
}
do {
let result = try decoder.decode(T.self, from: data)
return .success(result)
} catch(let error) {
return .failure(.decodingError)
}
} catch {
return .failure(.networkError)
}
}
}
And that’s pretty much it. To use these snippets, I create a new struct or class that implements the Endpoint protocol and uses the performRequest from the networking struct to make the api call.