Jessica Kimble
Jessica Kimble

Reputation: 545

Codable for API request

How would I make this same API request through codables? In my app, this function is repeated in every view that makes API calls.

func getOrders() {

        DispatchQueue.main.async {

            let spinningHUD = MBProgressHUD.showAdded(to: self.view, animated: true)
            spinningHUD.isUserInteractionEnabled = false

            let returnAccessToken: String? = UserDefaults.standard.object(forKey: "accessToken") as? String

            let access  = returnAccessToken!
            let headers = [
                "postman-token": "dded3e97-77a5-5632-93b7-dec77d26ba99",
                "Authorization": "JWT \(access)"
            ]

            let request = NSMutableURLRequest(url: NSURL(string: "https://somelink.com")! as URL,
                                              cachePolicy: .useProtocolCachePolicy,
                                              timeoutInterval: 10.0)

            request.httpMethod          = "GET"
            request.allHTTPHeaderFields = headers

            let session  = URLSession.shared
            let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
                if (error != nil) {
                    print(error!)

                } else {
                    if let dataNew = data, let responseString = String(data: dataNew, encoding: .utf8) {
                        print("----- Orders -----")
                        print(responseString)
                        print("----------")

                        let dict = self.convertToDictionary(text: responseString)
                        print(dict?["results"] as Any)
                        guard let results = dict?["results"] as? NSArray else { return }
                        self.responseArray = (results) as! [HomeVCDataSource.JSONDictionary]

                        DispatchQueue.main.async {
                            spinningHUD.hide(animated: true)
                            self.tableView.reloadData()
                        }

                    }

                }

            })

            dataTask.resume()
        }
    }

Upvotes: 3

Views: 3088

Answers (3)

karem_gohar
karem_gohar

Reputation: 336

I would suggest to do the following

  1. Create Base Service as below

import UIKit
import Foundation

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

class BaseService {

    var session: URLSession!

    // MARK: Rebuilt Methods
    func FireGenericRequest<ResponseModel: Codable>(url: String, methodType: MethodType, headers: [String: String]?, completion: @escaping ((ResponseModel?) -> Void)) {
        UIApplication.shared.isNetworkActivityIndicatorVisible = true

        // Request Preparation
        guard let serviceUrl = URL(string: url) else {
            print("Error Building URL Object")
            return
        }
        var request = URLRequest(url: serviceUrl)
        request.httpMethod = methodType.rawValue

        // Header Preparation
        if let header = headers {
            for (key, value) in header {
                request.setValue(value, forHTTPHeaderField: key)
            }
        }

        // Firing the request
        session = URLSession(configuration: URLSessionConfiguration.default)
        session.dataTask(with: request) { (data, response, error) in
            DispatchQueue.main.async {
                UIApplication.shared.isNetworkActivityIndicatorVisible = false
            }
            if let data = data {
                do {
                    guard let object = try? JSONDecoder().decode(ResponseModel.self , from: data) else {
                        print("Error Decoding Response Model Object")
                        return
                    }
                    DispatchQueue.main.async {
                        completion(object)
                    }
                }
            }
        }.resume()
    }

    private func buildGenericParameterFrom<RequestModel: Codable>(model: RequestModel?) -> [String : AnyObject]? {
        var object: [String : AnyObject] = [String : AnyObject]()
        do {
            if let dataFromObject = try? JSONEncoder().encode(model) {
                object = try JSONSerialization.jsonObject(with: dataFromObject, options: []) as! [String : AnyObject]
            }
        } catch (let error) {
            print("\nError Encoding Parameter Model Object \n \(error.localizedDescription)\n")
        }
        return object
    }

}


the above class you may reuse it in different scenarios adding request object to it and passing any class you would like as long as you are conforming to Coddle protocol

  1. Create Model Conforming to Coddle protocol

class ExampleModel: Codable {
    var commentId : String?
    var content : String?

    //if your JSON keys are different than your property name
    enum CodingKeys: String, CodingKey {
        case commentId = "CommentId" 
        case content = "Content"
    }

}

  1. Create Service to the specific model with the endpoint constants subclassing to BaseService as below

class ExampleModelService: BaseService<ExampleModel/* or [ExampleModel]*/> {

    func GetExampleModelList(completion: ((ExampleModel?)/* or [ExampleModel]*/ -> Void)?) {
        super.FireRequestWithURLSession(url: /* url here */, methodType: /* method type here */, headers: /* headers here */) { (responseModel) in
            completion?(responseModel)
        }
    }

}

  • Usage

class MyLocationsController: UIViewController {

    // MARK: Properties
    // better to have in base class for the controller
    var exampleModelService: ExampleModelService = ExampleModelService()

    // MARK: Life Cycle Methods
    override func viewDidLoad() {
        super.viewDidLoad()
        exampleModelService.GetExampleModelList(completion: { [weak self] (response) in
            // model available here
        })
    }
}

Upvotes: 2

erkutbas
erkutbas

Reputation: 303

First of all, I can recommend you to use this application -quicktype- for turning json file to class or struct (codable) whatever you want. enter link description here.

After that you can create a generic function to get any kind of codable class and return that as a response.

func taskHandler<T:Codable>(type: T.Type, useCache: Bool, urlRequest: URLRequest, completion: @escaping (Result<T, Error>) -> Void) {
    let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
        if let error = error {
            print("error : \(error)")
        }
        if let data = data {
            do {
                let dataDecoded = try JSONDecoder().decode(T.self, from: data)
                completion(.success(dataDecoded))
                // if says use cache, let's store response data to cache
                if useCache {
                    if let response = response as? HTTPURLResponse {
                        self.storeDataToCache(urlResponse: response, urlRequest: urlRequest, data: data)
                    }
                }
            } catch let error {
                completion(.failure(error))
            }
        } else {
            completion(.failure(SomeError))
        }
    }
    task.resume()
}

Upvotes: 0

Reinier Melian
Reinier Melian

Reputation: 20804

Basically, you need to conform Codable protocol in your model classes, for this you need to implement 2 methods, one for code your model and another for decode your model from JSON

func encode(to encoder: Encoder) throws

required convenience init(from decoder: Decoder) throws

After that you will be able to use JSONDecoder class provided by apple to decode your JSON, and return an array (if were the case) or an object of your model class.

class ExampleModel: Codable {
    var commentId : String?
    var content : String?

    //if your JSON keys are different than your property name
    enum CodingKeys: String, CodingKey {
        case commentId = "CommentId" 
        case content = "Content"
    }

}

Then using JSONDecoder you can get your model array like this

do {
    var arrayOfOrders : [ExampleModel] = try JSONDecoder().decode([ExampleModel].self, from: dataNew)                           
    }
    catch {
    }

Upvotes: 1

Related Questions