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
enum URLScheme: String { case http case https }
enum URLHost: String {
case live = "jsonplaceholder.typicode.com"
var host: String {
return self.rawValue
}
var fixedPath: String {
return ""
}
}
//https://jsonplaceholder.typicode.com/users
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
}
}
struct AppConfig {
static let scheme: URLScheme = .https
static let host: URLHost = .live
}
let path: URLPath = URLPath.user
print(path.url!)
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
}
}
}
enum AppError: Error {
case missingData
case serverError(Error)
case sessionExpired
case notReachable
}
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
}
}
}
class RequestToken {
private weak var task: URLSessionTask?
init(task: URLSessionTask?) {
self.task = task
}
func cancel() {
self.task?.cancel()
}
}
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)
}
}
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)
}
}
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)
}
}
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)}"
}
}
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)
}
}
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()