Consume RESTful APIs using Alamofire with Swift 3

In my previous post, I wrote about how to design a RESTful API in a correct way under the lightning of best practices. In this post, I will show you how to consume a RESTful API using a famous networking libray Alamofire using Swift language.

The most important elements for the RESTful APIs are the consepts of a Resource and the Actions on them. The actions for a RESTful API is the CRUD operations on any resource. So in this tutorial, I will cover get (GET), create (POST), update (PUT) and delete (DELETE) operations on a resource serving from a designated API endpoints.

Let’s say we have API endpoints for a patient resource that for a hospital API.

  • api.resthospital.com/api/patients (GET)
  • api.resthospital.com/api/patient (POST)
  • api.resthospital.com/api/patients/{id} (PUT)
  • api.resthospital.com/api/patients/{id} (DELETE)

Since RESTful architecture is based on HTTP request we should leverage the HTTP status codes. The following piece of code enumurates some known status codes:

public enum EHttpStatusCode : Int {
    case OK = 200                       // /GET, /DELETE result is successful
    case CREATED = 201                  // /POST or /PUT is successful
    case NOT_MODIFIED = 304             // If caching is enabled and etag matches with the server
    case BAD_REQUEST = 400              // Possibly the parameters are invalid
    case INVALID_CREDENTIAL = 401       // INVALID CREDENTIAL, possible invalid token
    case NOT_FOUND = 404                // The item you looked for is not found
    case CONFLICT = 409                 // Conflict - means already exist
    case AUTHENTICATION_EXPIRED = 419   // Expired
}

RESTful API will provide us data with a format like Json (or Xml, …). Since we obtain data in one of these format, we should somehow convert the data into models (classes/structs defined to model the domain). We can write our model converters providing from/to json/xml converters or use beatiful open-source libraries. ObjectMapper is one of those beautiful libraries with a clear usage. Here, i used AlamofireObjectMapper which is an Alamofire extension which converts JSON response data into swift objects using ObjectMapper.

For instance, we can model a patient like below (aware the Mappable protocol)

import Foundation
import ObjectMapper

class Country: Mappable {
    var id = ""
    var name = ""
    var age = ""

    init(id: String, name: String, age: String) {
        self.id = id
        self.name = name
        self.age = age
    }

    required init?(map: Map){

    }

    func mapping(map: Map) {
        id <- map["id"]
        name <- map["name"]
        age <- map["age"]
    }
}

Okay, we have the domain model, and thanks to ObjectMapper library for the json conversions. Before creating our ApiService class, let’s create a protocol named ApiServiceProtocol to see the blueprints of our Api Service

protocol ApiServiceProtocol {
    func get(url: String, headers: [String : String]?, callback: @escaping (ECallbackResultType) -> Void)
    func update(url: String, parameters: [String : Any]?, headers: [String : String]?, callback: @escaping (ECallbackResultType) -> Void)
    func create(url: String, parameters: [String : Any]?, headers: [String : String]?, callback: @escaping (ECallbackResultType) -> Void)
    func delete(url: String, parameters: [String : Any]?, headers: [String : String]?, callback: @escaping (ECallbackResultType) -> Void)
}

Let’s create our ApiService class. Create a class named ApiService conforming to ApiServiceProtocol protocol. Do you see the ? Yeah, In order to create a reusable api service with providing compatibility to all Mappable class we defined for our domain, I use Generics

class ApiService<T: BaseMappable>: ApiServiceProtocol {
    // Implement the protocol
}

Let’s start to implement for fetching data from a restful api

1. Fetching data (GET request)

GET request is best fit for fetching data with its idempotent behaviour, no affect on status of the api.

/**
This method sends a GET request to given url in order to fetch data
- parameters:
    - url: API Endpoint
    - headers: Required HTTP header
    - callback: The callback handler to provide the result of the fetched data
*/
func get(url: String, headers: [String : String]?, callback: @escaping (ECallbackResultType) -> Void) {
    // Send the GET request
    Alamofire.request(url, method: .get, headers: headers).responseObject { (response: DataResponse<T>) -> Void in
        // Get the status code of response
            if (response.response != nil) {
                let status = response.response!.statusCode;

                switch status {

                case EHttpStatusCode.OK.rawValue:
                // Get the response body
                if let value = response.result.value {
                // call callback with no error
                callback(ECallbackResultType.Success(value as T!))
                }

                default:
                callback(ECallbackResultType.Failure(ECallbackErrorType.UnknownError))
            }
        } else {
            callback(ECallbackResultType.Failure(ECallbackErrorType.APIEndpointNotAvailable))
        }
    }
}

The critical points here are using DataResponse where [AlamofireObjectMapper](https://github.com/tristanhimmelman/AlamofireObjectMapper) provides the DataResponse and Generics provides ability to use this method with a model-independent way.

2. Updating data (PUT request)

This is the same with get function except http method: PUT

/**
This method sends a PUT request to given url in order to update data
- parameters:
    - url: API endpoint
    - parameters: The data to be updated
    - headers: Required HTTP header
    - callback: The callback handler to provide the result of the update operation
*/
func update(url: String, parameters: [String : Any]?, headers: [String : String]?, callback: @escaping (ECallbackResultType) -> Void) {
    // Send the POST request
    Alamofire.request(url, method: .put, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
    .responseJSON { response in

        if (response.response != nil) {
            // Get the status code of response
            let status = response.response!.statusCode;

            switch status {

                case EHttpStatusCode.OK.rawValue:
                    // Successful - No error
                    callback(ECallbackResultType.Success(nil))

                case EHttpStatusCode.INVALID_CREDENTIAL.rawValue:
                    // invalid credentials
                    callback(ECallbackResultType.Failure(ECallbackErrorType.InvalidCredentials))

                case EHttpStatusCode.CONFLICT.rawValue:
                    // Resource already exist
                    callback(ECallbackResultType.Failure(ECallbackErrorType.AlreadyExist))

                case EHttpStatusCode.NOT_FOUND.rawValue:
                    // The resource does not exist
                    callback(ECallbackResultType.Failure(ECallbackErrorType.NotExist))

                case EHttpStatusCode.BAD_REQUEST.rawValue:
                    // Invalid parameters
                    callback(ECallbackResultType.Failure(ECallbackErrorType.InvalidParameters))

                default:
                    callback(ECallbackResultType.Failure(ECallbackErrorType.UnknownError))
            }
        } else {
            callback(ECallbackResultType.Failure(ECallbackErrorType.APIEndpointNotAvailable))
        }
    }
}

3/4. Creating and Deleting data

It is your homework :)

References

comments powered by Disqus