Skip to content

Instantly share code, notes, and snippets.

@ptseng
Last active November 24, 2022 21:38
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save ptseng/85f0aa8acbf48c0a64a68f4e44081a4d to your computer and use it in GitHub Desktop.
Save ptseng/85f0aa8acbf48c0a64a68f4e44081a4d to your computer and use it in GitHub Desktop.
Code Sample for Leveraging Structs and Generics in the Networking Layer with Swift 4 (An update to objc.io Swift Talk)
import Foundation
import UIKit
import PlaygroundSupport
// MARK: HttpMethod
enum HttpMethod<Body> {
case get
case post(Body)
}
extension HttpMethod {
var methodString: String {
switch self {
case .get: return "GET"
case .post: return "POST"
}
}
func map<B>(f: (Body) -> B) -> HttpMethod<B> where B: Encodable {
switch self {
case .get: return .get
case .post(let body): return .post(f(body))
}
}
}
// MARK: Resource
struct Resource<A> where A: Decodable {
let method: HttpMethod<Data>
let url: URL
let parse: (Data) -> A? = { data in return try? JSONDecoder().decode(A.self, from: data) }
}
extension Resource {
init<T>(method: HttpMethod<T>, url: URL) where T: Encodable {
self.method = method.map { json in try! JSONEncoder().encode(json) }
self.url = url
}
}
// MARK: ImageResource
struct ImageResource {
let url: URL
let method: HttpMethod<Data>
let parse: (Data) -> UIImage?
}
extension ImageResource {
init(imageUrl: URL) {
self.url = imageUrl
self.method = .get
self.parse = { data in return UIImage(data: data) }
}
}
// MARK: Networking
extension URLRequest {
init<A>(resource: Resource<A>) {
self.init(url: resource.url)
httpMethod = resource.method.methodString
if case let .post(data) = resource.method { httpBody = data }
}
init(imageResource: ImageResource) {
self.init(url: imageResource.url)
httpMethod = imageResource.method.methodString
}
}
final class Networking {
func load<A>(resource: Resource<A>, completion: @escaping (A?, URLRequest?, Error?) -> ()) {
let request = URLRequest(resource: resource)
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(nil, request, error)
return
}
completion(data.flatMap(resource.parse), request, nil)
}.resume()
}
func loadImage(resource: ImageResource, completion: @escaping (UIImage?) -> ()) {
let request = URLRequest(imageResource: resource)
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
debugPrint(error)
completion(nil)
return
}
completion(data.flatMap(resource.parse))
}.resume()
}
}
// Playground
// Get
struct IPAddress: Decodable {
let ip: String
}
let ipResource = Resource<IPAddress>(method: .get, url: URL(string: "https://api.ipify.org/?format=json")!)
Networking().load(resource: ipResource) { ipaddress, _, _ in
guard let ipaddress = ipaddress else { return }
print("\(ipaddress.ip) IP")
}
// Post
struct Company: Decodable {
let name: String
let email: String
}
let request = ["email": "hello@metaltoad.com"]
let companyResource = Resource<Company>(method: .post(request), url: URL(string: "/company/email")!)
Networking().load(resource: companyResource) { company, _, _ in
guard let company = company else { return }
// print(company.name)
}
// Get Image
let imageResource = ImageResource(imageUrl: URL(string: "https://www.staples-3p.com/s7/is/image/Staples/s1000625_sc7")!)
Networking().loadImage(resource: imageResource) { image in
guard let image = image else { return }
image
}
PlaygroundPage.current.needsIndefiniteExecution = true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment