Using Combine 2

  1. API End point
//
//  APIEndpoint.swift
//  FutureAPIDemo
//
//  Created by Joynal Abedin on 3/11/23.
//

import Foundation

///APIEndpoint
protocol APIEndpoint {
    
    var baseURL: URL { get }
    var path: String { get }
    var method: HTTPMethod { get }
    var headers: [String: String]? { get }
    var parameters: [String: String]? { get }
}

///HTTPMethod
enum HTTPMethod: String {
    case get = "GET"
    case post = "POST"
    case put = "PUT"
    case patch = "PATCH"
    case delete = "DELETE"
}

//MARK: - URLEndpoint
enum URLEndpoint: APIEndpoint {
    
    ///all api` cases`
    case bookList
    case searchBook(name: String)
    
    ///ap `baseURL`
    var baseURL: URL {
        guard let url = URL(string: "https://devilarticle.com") else {
            fatalError()
        }
        return url
    }
    
    ///api `endPoint`
    var path: String {
        switch self {
        case .bookList:
            return "/api/LibraryService/GetAllBook"
        case .searchBook:
            return "/api/LibraryService/SearchBook"
        }
    }
    
    ///api `method`
    var method: HTTPMethod {
        switch self {
        case .bookList:
            return .get
        case .searchBook:
            return .get
        }
    }
    
    ///api `headers`
    var headers: [String: String]? {
        switch self {
        case .bookList:
            return ["Content-Type": "application/json"]
        case .searchBook(name: _):
            return ["Content-Type": "application/json"]
        }
    }
    
    ///api `parameters`
    var parameters: [String: String]? {
        switch self {
        case .bookList:
            return nil
        case .searchBook(name: let name):
            return ["name": name]
        }
    }
    
}

2. API Service

//
//  APIService.swift
//  FutureAPIDemo
//
//  Created by Joynal Abedin on 3/11/23.
//

import Foundation
import Combine

///API Response errors
enum APIError: Error {
    case invalidResponse
    case invalidData
}

///protocol
protocol APIClient {
    associatedtype EndpointType: APIEndpoint
    func request<T: Decodable>(_ endpoint: EndpointType) -> AnyPublisher<T, Error>
}

//MARK: - APIService
class APIService<EndpointType: APIEndpoint>: APIClient {
    
    func request<T: Decodable>(_ endpoint: EndpointType) -> AnyPublisher<T, Error> {
        
        let baseAppend = endpoint.baseURL.appendingPathComponent(endpoint.path).absoluteString.removingPercentEncoding
        var request = URLRequest(url: URL(string: baseAppend!)!)
        
        ///added api endpoint
        request.httpMethod = endpoint.method.rawValue
        /// Set up any request `headers` or `parameters` here
        endpoint.headers?.forEach { request.addValue($0.value, forHTTPHeaderField: $0.key) }
        endpoint.parameters?.forEach {
            request.url?.append(queryItems: [URLQueryItem(name: $0.key, value: $0.value)])
        }
        
        return Future<T, Error> { promise in
            URLSession.shared.dataTask(with: request) { (data, response, _) in
                DispatchQueue.main.async {
                    do {
                        guard let data = data else {
                            return promise(.failure("Something went wrong" as! Error))
                        }
                        guard let httpResponse = response as? HTTPURLResponse,
                              (200...299).contains(httpResponse.statusCode) else {
                            throw APIError.invalidResponse
                        }
                        let decodedData = try JSONDecoder().decode(T.self, from: data)
                        return promise(.success(decodedData))
                    } catch let error {
                        return promise(.failure(error))
                    }
                }
            }.resume()
        }
        .subscribe(on: DispatchQueue.global(qos: .background))
        .eraseToAnyPublisher()
    }
}

3. API Model

//
//  Response.swift
//  DataTaskAPIDemo
//
//  Created by Joynal Abedin on 1/11/23.
//

import Foundation

struct Response: Codable {
    var responseCode: Int
    var result: String
    var errormessage: String?
    var data: [BookList]
}

struct BookList: Codable {
    var id: Int
    var bookId: Int
    var bookName: String
    var availableCopyNumber: Int
}

//
struct ToDo: Decodable {
  let userId: Int
  let id: Int
  let title: String
  let isComplete: Bool
  
  enum CodingKeys: String, CodingKey {
    case isComplete = "completed"
    case userId, id, title
  }
}

4. Get Data

//
//  ContentVM.swift
//  FutureAPIDemo
//
//  Created by Joynal Abedin on 3/11/23.
//

import Foundation
import Combine

class ContentVM: ObservableObject {
    
    enum ViewState {
        case START
        case LOADING
        case SUCCESS(books: Response)
        case FAILURE(error: String)
    }
    
    @Published var currentState: ViewState = .START
    private var cancelables = Set<AnyCancellable>()
    
    let apiClient = APIService<URLEndpoint>()
    
    init() {
        getBookList()
    }
    
    func getBookList() {
        self.currentState = .LOADING
        apiClient.request(URLEndpoint.bookList)
            .sink { completion in
                switch completion {
                case .finished:
                    print("Execution Finihsed.")
                case .failure(let error):
                    self.currentState = .FAILURE(error: error.localizedDescription)
                }
            } receiveValue: { users in
                self.currentState = .SUCCESS(books: users)
            }.store(in: &cancelables)
    }
}