Skip to content

Instantly share code, notes, and snippets.

@zafarivaev
Created August 7, 2020 20:35
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save zafarivaev/c9ffa6e47b302795fa3413fb5901b72e to your computer and use it in GitHub Desktop.
Save zafarivaev/c9ffa6e47b302795fa3413fb5901b72e to your computer and use it in GitHub Desktop.
import PlaygroundSupport
import Foundation
import Combine
// MARK: - Network Controller
protocol NetworkControllerProtocol: class {
typealias Headers = [String: Any]
func get<T>(type: T.Type,
url: URL,
headers: Headers
) -> AnyPublisher<T, Error> where T: Decodable
}
final class NetworkController: NetworkControllerProtocol {
func get<T: Decodable>(type: T.Type,
url: URL,
headers: Headers
) -> AnyPublisher<T, Error> {
var urlRequest = URLRequest(url: url)
headers.forEach { (key, value) in
if let value = value as? String {
urlRequest.setValue(value, forHTTPHeaderField: key)
}
}
return URLSession.shared.dataTaskPublisher(for: urlRequest)
.map(\.data)
.decode(type: T.self, decoder: JSONDecoder())
.eraseToAnyPublisher()
}
}
// MARK: - Endpoint
/// Reusable base Endpoint struct
struct Endpoint {
var path: String
var queryItems: [URLQueryItem] = []
}
/// Dummy API specific Endpoint extension
extension Endpoint {
var url: URL {
var components = URLComponents()
components.scheme = "https"
components.host = "dummyapi.io"
components.path = "/data/api" + path
components.queryItems = queryItems
guard let url = components.url else {
preconditionFailure("Invalid URL components: \(components)")
}
return url
}
var headers: [String: Any] {
return [
"app-id": "b8E57Ts56PBNGbF4fvCP"
]
}
}
/// Dummy API endpoints
extension Endpoint {
static var users: Self {
return Endpoint(path: "/user")
}
static func users(count: Int) -> Self {
return Endpoint(path: "/user",
queryItems: [
URLQueryItem(name: "limit",
value: "\(count)")
]
)
}
static func user(id: String) -> Self {
return Endpoint(path: "/user/\(id)")
}
}
// MARK: - Codable Models
struct Users: Codable, CustomStringConvertible {
let data: [User]?
}
struct User: Codable, CustomStringConvertible {
let id: String?
let title: String?
let firstName: String?
let lastName: String?
let email: String?
let picture: String?
}
// MARK: - Debugging Helper
extension CustomStringConvertible where Self: Codable {
var description: String {
var description = "\n***** \(type(of: self)) *****\n"
let selfMirror = Mirror(reflecting: self)
for child in selfMirror.children {
if let propertyName = child.label {
description += "\(propertyName): \(child.value)\n"
}
}
return description
}
}
// MARK: - Logic Controller
protocol UsersLogicControllerProtocol: class {
var networkController: NetworkControllerProtocol { get }
func getUsers() -> AnyPublisher<Users, Error>
func getUsers(count: Int) -> AnyPublisher<Users, Error>
func getUser(id: String) -> AnyPublisher<User, Error>
}
final class UsersLogicController: UsersLogicControllerProtocol {
let networkController: NetworkControllerProtocol
init(networkController: NetworkControllerProtocol) {
self.networkController = networkController
}
func getUsers() -> AnyPublisher<Users, Error> {
let endpoint = Endpoint.users
return networkController.get(type: Users.self,
url: endpoint.url,
headers: endpoint.headers)
}
func getUsers(count: Int) -> AnyPublisher<Users, Error> {
let endpoint = Endpoint.users(count: count)
return networkController.get(type: Users.self,
url: endpoint.url,
headers: endpoint.headers)
}
func getUser(id: String) -> AnyPublisher<User, Error> {
let endpoint = Endpoint.user(id: id)
return networkController.get(type: User.self,
url: endpoint.url,
headers: endpoint.headers)
}
}
// MARK: - Usage Example
let networkController = NetworkController()
let usersLogicController = UsersLogicController(networkController: networkController)
var subscriptions = Set<AnyCancellable>()
usersLogicController.getUsers()
.sink(receiveCompletion: { (completion) in
switch completion {
case let .failure(error):
print("Couldn't get users: \(error)")
case .finished: break
}
}) { users in
print(users)
}
.store(in: &subscriptions)
PlaygroundPage.current.needsIndefiniteExecution = true
@Axort
Copy link

Axort commented Feb 11, 2021

How would a post request look like?

Here is my take on it:

protocol NetworkControllerProtocol: class {
    func post<T, U>(url: URL,
                     headers: Headers,
                     payload: T) -> AnyPublisher<U, Error> where T: Codable, U: Decodable
}
final class NetworkController: NetworkControllerProtocol {
func post<T, U>(url: URL,
                 headers: Headers,
                 payload: T) -> AnyPublisher<U, Error> where T: Codable, U: Decodable {
        var urlRequest = URLRequest(url: url)
        
        headers.forEach { (key, value) in
            if let value = value as? String {
                urlRequest.setValue(value, forHTTPHeaderField: key)
            }
        }
        
        let encoder = JSONEncoder()
        do {
            let jsonData = try encoder.encode(payload)
            urlRequest.httpBody = jsonData
        }catch {
            print("\(error.localizedDescription)")
        }
        
        return URLSession.shared.dataTaskPublisher(for: urlRequest)
            .map(\.data)
            .decode(type: U.self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
    }
}

Not sure if I'm doing it right, what do you think?

@Jitender-k123
Copy link

AnyPublisher is misssing.

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