Skip to content

Instantly share code, notes, and snippets.

@NeilsUltimateLab
Last active April 5, 2021 03:30
Show Gist options
  • Save NeilsUltimateLab/a68ff7d8c0a7446cbe4755211b757d9e to your computer and use it in GitHub Desktop.
Save NeilsUltimateLab/a68ff7d8c0a7446cbe4755211b757d9e to your computer and use it in GitHub Desktop.
Fetch resources using Alamofire with Codable.

CodableResource

Fetch resource using Alamofire with Codable.

This document shows the use of URLComponenent to decouple url by use of swift enums. By creating a Result<A> enum type error handling is done with ease. At the end defining the Codable User and Address structs allows the easy json-parsing.

import Foundation

URLScheme to declare the HTTP Scheme

enum URLScheme: String { case http case https }

URLHost defines the URLHost

enum URLHost: String {
    case live = "jsonplaceholder.typicode.com"
    
    var host: String {
        return self.rawValue
    }
    
    var fixedPath: String {
        return ""
    }
}

Using jsonplaceholder's users resource.

//https://jsonplaceholder.typicode.com/users

URLPath defines endpoint of a request.

enum URLPath {
    case user
    case logout(id: Int)
    case update(userId: Int)
    
    var path: String {
        switch self {
        case .user:
            return "/users"
        case let .logout(id: userId):
            return "/users/\(userId)/logout"
        case let .update(userId: userId):
            return "/users/\(userId)/update"
        }
    }
    
    var url: URL? {
        var urlComponent = URLComponents()
        urlComponent.scheme = AppConfig.scheme.rawValue
        urlComponent.host = AppConfig.host.rawValue
        urlComponent.path = AppConfig.host.fixedPath + self.path
        return urlComponent.url
    }
}

Global scheme declaration

struct AppConfig {
    static let scheme: URLScheme = .https
    static let host: URLHost = .live
}
let path: URLPath = URLPath.user
print(path.url!)

Web-request http methods

typealias JSONType = [String : Any]

enum HTTPMethod {
    case get
    case post(JSONType?)
    case put(JSONType?)
    
    var requestMethod: Alamofire.HTTPMethod {
        switch self {
        case .get:
            return Alamofire.HTTPMethod.get
        case .post(_):
            return Alamofire.HTTPMethod.post
        case .put(_):
            return Alamofire.HTTPMethod.put
        }
    }
    
    var parameter: JSONType? {
        switch self {
        case let .post(parameter):
            return parameter
        case let .put(parameter):
            return parameter
        default:
            return nil
        }
    }
}

Application Error cases

enum AppError: Error {
    case missingData
    case serverError(Error)
    case sessionExpired
    case notReachable
}

Result type for the response handling

enum Result<A> {
    case value(A)
    case error(AppError)
    
    var error: AppError? {
        switch self {
        case .error(let b):
            return b
        default:
            return nil
        }
    }
    
    var value: A? {
        switch self {
        case .value(let a):
            return a
        default:
            return nil
        }
    }
}

RequestToken to return URLSessionTask of the request

class RequestToken {
    private weak var task: URLSessionTask?
    
    init(task: URLSessionTask?) {
        self.task = task
    }
    
    func cancel() {
        self.task?.cancel()
    }
}

A generic structure for web-request parameters.

struct WebResource<A> {
    var urlPath: URLPath
    var method: HTTPMethod = .get
    var header: [String : String]? = nil
    var decode: (Data) -> Result<A>
    
    func request(completion: @escaping (Result<A>)->Void) -> RequestToken {
        return WebResourceManager.shared.fetchRequest(resource: self, completion: completion)
    }
}

A Generic Decodable extension to decode Data -> T

extension Decodable {
    static func decode<T: Codable>(_ data: Data) -> Result<T> {
        if let decoded = try? JSONDecoder().decode(T.self, from: data) {
            return .value(decoded)
        }
        return .error(.missingData)
    }
}

Alamofire web-request

import Alamofire

class WebResourceManager {
    static let shared = WebResourceManager()
    
    func fetchRequest<A>(resource: WebResource<A>, completion: @escaping (Result<A>)->Void) -> RequestToken {
        let url = resource.urlPath.url!
        let header = resource.header
        let parameter = resource.method.parameter
        
        let dataRequest = Alamofire.request(url, method: resource.method.requestMethod, parameters: parameter, encoding: JSONEncoding.default, headers: header).responseData { (response) in
            
            if let code = response.response?.statusCode {
                if 200...299 ~= code {
                    print("All well")
                }
                if 300...399 ~= code {
                    print("Huhh")
                }
                if 400...499 ~= code {
                    let nserror = NSError(domain: "com.neilsultimatelab.errorbase", code: code, userInfo: nil)
                    completion(.error(.serverError(nserror)))
                    return
                }
            }
            
            if let error = response.result.error {
                print(error)
                completion(.error(.serverError(error)))
                return
            }
            if response.result.isSuccess {
                if let data = response.result.value {
                    completion(resource.decode(data))
                    return
                }
            }else {
                completion(.error(.missingData))
                return
            }
        }
        
        let task = dataRequest.task
        
        return RequestToken(task: task)
    }
}

Modals

struct Address: Codable {
    var street: String?
    var suite: String?
    var city: String?
    var zipcode: String?
}

struct Company: Codable {
    var name: String?
    var catchPhrase: String?
    var bs: String?
}

extension Company: CustomDebugStringConvertible {
    var debugDescription: String {
        return "name: \(name ?? ""), catchPhrase: \(catchPhrase ?? ""), bs: \(bs ?? "")"
    }
}

struct User: Codable {
    var id: Int?
    var name: String?
    var username: String?
    var email: String?
    var company: Company?
    var address: Address?
}

extension User: CustomDebugStringConvertible {
    var debugDescription: String {
        return "\n{id:\(id ?? 0), name:\(name ?? ""), username:\(username ?? ""), email:\(email ?? ""), company: \(company ?? nil), address: \(address ?? nil)}"
    }
}

Defining fetchResource method to get [User] back.

extension User {
    static func fetchResource(completion: @escaping (Result<[User]>)->Void) -> RequestToken {
        let webResource = WebResource<[User]>(urlPath: .user, method: .get, header: nil, decode: (User.decode))
        return WebResourceManager.shared.fetchRequest(resource: webResource, completion: completion)
    }
}

Implementation for the User's fetch request.

import PlaygroundSupport

func fetchUsers() {
    PlaygroundPage.current.needsIndefiniteExecution = true
    
    User.fetchResource { (result) in
        PlaygroundPage.current.needsIndefiniteExecution = false
        if let error = result.error {
            print(error)
        }
        if let value = result.value {
            print(value)
        }
    }
}

fetchUsers()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment