Swift3: Consming REST services, data mapping and intro to CocoaPods

In my first Swift post I'll explain how to consume a simple REST service, parse the JSON response and map it into our own objects using some libraries and generics. Also it includes the implementation of a singleton class to keep our code and service calls organized.

The service to implement is the one located here: https://api.danielmg.org/api/asoiaf/houses

We are going to make use of CocoaPods to install the required libraries. These are very lightweight, easy to install and make our tasks way easier especially for parsing the response.

Table of Contents

Installing CocoaPods and Swift libraries

CocoaPods is a package/dependency manager that allows you to install libraries for your Swift projects. It works similar to NPM, where you need to create a simple configuration file (a Podfile, in your project folder) where you have to specify the libraries that you want to install and then run an install command.

CococaPods is a Ruby gem, so you need to have that installed before. To install it and just like any other gem you gotta type:

gem install cocoapods

Creating the Podfile

If you are working on a new project FIRST you need to create the Xcode project. Then open your Terminal and navigate to your project folder.

First we need to create the Podfile, simply by typing:

pod init

That command outputs nothing but it creates the file. Then in your favorite editor open the file and add the TRON and SwiftyJSON lines:

# Podfile

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'Simple Rest Client' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Networking library (https://github.com/MLSDev/TRON)
  pod 'TRON', '~> 2.0.0'

  # JSON parsing library (https://github.com/SwiftyJSON/SwiftyJSON)
  pod 'SwiftyJSON'
end

Installing the Libraries

After that, and to install the new packages just type the following:

pod install

This command may take a while, and will dump lots of data but the important thing that you need to pay attention is:

[!] Please close any current Xcode sessions and use `Simple Rest Client.xcworkspace` for this project from now on.

From now on you need to open the project using that *.xcworkspace file.

Performing requests and Generics

Performing requests using TRON is really simple, just check the code snippet bellow. The key parts are:

  • The two generics: MyJsonData and MyJsonError are needed for APIRequest; these are the two classes that we need to implement ourselves, both receive a JSON object that we can use to map into our own objects.
  • The perform() call: with two completion blocks in case of success or error. Both will receive as parameters the objects: MyJsonData if success, or MyJsonError otherwise.

Bellow you can check how to implement these two generics.

import TRON
import SwiftyJSON

let tron = TRON(baseURL: "https://api.danielmg.org/api")
let request: APIRequest<MyJsonData, MyJsonError> = tron.request("/asoiaf/houses")

// Asynchronous call
request.perform(withSuccess: { (housesData) in
  // housesData: This is of type: MyJsonData
  print("Got the data from the server", housesData)
}) { (err) in
  // err: This is of type: MyJsonError
  print("Error fetching Houses", err)
}

Implementing our model

Our service consist of 3 main fields, two strings and an array of elements. The array is what we are interested in; so check the snippet to see how it looks. (ps: is way longer online!):

{
  "status":"OK",
  "message":"Here is a list of ASOIAF's house mottos",
  "dataset":[
   {"id":"0001","name":"House Baratheon","motto":"Ours is the Fury"},
   {"id":"0007","name":"House Lannister","motto":"Hear Me Roar!"},
   {"id":"0004","name":"House Stark","motto":"Winter is Coming"}
  ]
}

Now let's write a simple structure that will represent each of the elements inside the array. The constructor receives a JSON object that we are going to use extract the information for each of the elements. In our services we assume all fields are non-optional.

# WesterosHouse.swift
import Foundation
import SwiftyJSON

struct WesterosHouse {
  let id : String
  let name : String
  let motto : String
    
  init(json: JSON) {
    id = json["id"].stringValue
    name = json["name"].stringValue
    motto = json["motto"].stringValue
  }
}

Implementing the Generics

These are the two generic classes that we need to implement to perform our request that i mentioned on the previous step. The code is pretty straight forward so I'm just gonna explain it within the comments.

// onError
class MyJsonError: JSONDecodable {
  required init(json: JSON) throws {
    throw NSError(domain: "com.danieluy.samples", code: 2,
                  userInfo: [NSLocalizedDescriptionKey: "MyServices: Failted to parse JSON."])
  }
}

// "onSucces" Data
class MyJsonData: JSONDecodable {
  let status : String
  let message : String
  let dataset : [WesterosHouse]
  
  // The initializer will recive a JSON object that we are
  // going to use to exctract the information for each of the elements.
  required init(json: JSON) throws {
    status = json["status"].stringValue
    message = json["message"].stringValue
    
    // This chunk of code is necessary to safely un-wrap our array
    // doing it this way will prevent some crashes
    guard let housesJsonArray = json["dataset"].array else {
      throw NSError(domain: "com.danieluy.samples", code: 3,
                    userInfo: [NSLocalizedDescriptionKey: "MyServices: Failed to parse Houses data."])
      }
      
      // Now that we have our array of data, let's just map it into an Array
      // of our own objects, passing the JSON data to the struct we created before.
      dataset = housesJsonArray.map{WesterosHouse(json: $0)}
    }
}

Creating a singleton for TRON

It's a good idea to create a Singleton for our services, that way you can keep our TRON stuff in the same place and prevent things from going out of control. On the next example I provide the basic structure for our example. Download the full working example down bellow.

// MyServices.swift
import Foundation
import TRON
import SwiftyJSON

struct MyServices {
  let tron = TRON(baseURL: "https://api.danielmg.org/api")

  /*
   This is our Singleton, allows you to use for example: 
   MyServices.single.fetchHouses()
  */
  static let singleton = MyServices()
  
  /* Simple method inside our singleton.
     I included a completion block (doneHandler) for returing the parsed data
  */
  func fetchHouses(doneHandler: @escaping ([WesterosHouse]) -> Void) {
    let request: APIRequest<MyServicesJsonData, MyServicesJsonError> = tron.request("/asoiaf/houses")

    request.perform(withSuccess: { (housesData) in
      doneHandler(housesData.dataset)
    }) { (err) in
       print ("MyServices: Error fetching Houses", err)
    }
  }
    
  class MyServicesJsonError: JSONDecodable {
    /* ... class implementation from our previous example here ... */
  }
    
  class MyServicesJsonData: JSONDecodable {
    /* ... class implementation from our previous example here ... */
  }
}

Download the project

I uploaded the working project directly to a Github so you can just copy-paste it without reading the article :)

The project was made using XCode 8 and Swift 3 for future reference.

Download the code here

About
Related Posts
  • No related articles have been found, for now
Comments
The comments are closed on this section.
Hire me!

I'm currently open for new projects or join pretty much any project i may fit in, let me know!

Read about what I do, or contact me for details.

Categories