Codementor Events

URLSession, Web API calls Reactive way (RxSwift, RxCocoa)

Published Nov 09, 2019
URLSession, Web API calls Reactive way (RxSwift, RxCocoa)

This post will explain how to use URLSession for your web API calls in reactive way. There are many ways you can write asynchronous code, like using NotificaitionCenter, delegate pattern, Grand Central Dispatch, Closures, and some of your code is already asynchronous like UI components.

All those ways are good for some use case that your application might need and choosing between them depends on your typical use case.
Today we will create API class that will deal with your web API calls in reactive way and in one place.

1. RxSwift introduction

Foundation of reactive way of programming is observable and observers. Observable represent class that can in any given period of time asynchronously produce sequence of events that can carry some data. This mean that observers can subscribe to observable class for emitted values from observable so they can react to those events in real time.
An Observable can emit (and observers can receive) these three types of events:
Next event: this event carry the latest data value.
Completed event: this event terminates the event sequence with success, and observable will not emit new events.
Error event: this event terminate observable with error and observable will not emit new events.

So if we want our observable to emit some event with values we need we need to subscribe to our observable.
Another important thing that we must do is to cancel observer subscription to observable. This is done automatically with completed or error events but we can manually do that with calling dispose() on our subscription. For this we have DisposeBag type that holds disposables and will call dispose() on each one when the dispose bag is about to be deallocated.

2. Create model that will carry data

To make our data types encodable and decodable for compatibility with external representations such as JSON, Swift standard library defines a standardised approach to data encoding and decoding. This means that our model must conform to Codable protocols. Adding Codable to the inheritance list for our model triggers an automatic conformance that satisfies all of the protocol requirements from Encodable and Decodable.

import Foundation
//MARK: PostModel
public struct PostModel: Codable {
  private var id: Int
  private var userId: Int 
  private var title: String
  private var body: String
  init(id: Int, userId: Int, title: String, body: String) {
     self.id = id
     self.userId = userId
     self.title = title
     self.body = body
  }
  func getId() -> Int {
    return self.id
  }
  func getUserId() -> Int {
    return self.userId
  }
  func getTitle() -> String {
    return self.title
  }
  func getBody() -> String {
    return self.body
  }
}

3. Create Request class

Now let create our Request class that will create our observable and implement our network logic using URLSession. Here we are going to use create operator. This operator takes a single parameter called subscribe. This parameter is an escaping closure that takes an AnyObserver and returns a Disposable.


import Foundation
import RxSwift
import RxCocoa
//MARK: RequestObservable class
public class RequestObservable {
  private lazy var jsonDecoder = JSONDecoder()
  private var urlSession: URLSession
  public init(config:URLSessionConfiguration) {
      urlSession = URLSession(configuration:  
                URLSessionConfiguration.default)
  }
//MARK: function for URLSession takes
  public func callAPI<ItemModel: Decodable>(request: URLRequest)
    -> Observable<ItemModel> {
  //MARK: creating our observable
  return Observable.create { observer in
  //MARK: create URLSession dataTask
  let task = self.urlSession.dataTask(with: request) { (data,
                response, error) in
  if let httpResponse = response as? HTTPURLResponse{
  let statusCode = httpResponse.statusCode
  do {
    let _data = data ?? Data()
    if (200...399).contains(statusCode) {
      let objs = try self.jsonDecoder.decode(ItemModel.self, from:  
                          _data)
      //MARK: observer onNext event
      observer.onNext(objs)
    }
    else {
      observer.onError(error!)
    }
  } catch {
      //MARK: observer onNext event
      observer.onError(error)
     }
   }
     //MARK: observer onCompleted event
     observer.onCompleted()
   }
     task.resume()
     //MARK: return our disposable
     return Disposables.create {
       task.cancel()
     }
   }
  }
}

4. Create WebAPI singleton

This class is responsible for creating http requests to our web api. Here we can add multiple type of requests depending on our web api methods. Here we will add just two of them, one get and one post. Our methods of course return observable that we will subscribe later in our controller.

import Foundation
import RxCocoa
import RxSwift
//MARK: extension for converting out RecipeModel to jsonObject
fileprivate extension Encodable {
  var dictionaryValue:[String: Any?]? {
      guard let data = try? JSONEncoder().encode(self),
      let dictionary = try? JSONSerialization.jsonObject(with: data,    
        options: .allowFragments) as? [String: Any] else {
      return nil
    }
    return dictionary
  }
}
class APIClient {
  static var shared = APIClient()
  lazy var requestObservable = RequestObservable(config: .default)
  func getRecipes() throws -> Observable<[PostModel]> {
    var request = URLRequest(url:    
          URL(string:"https://jsonplaceholder.typicode.com/posts")!)
    request.httpMethod = "GET"
    request.addValue("application/json", forHTTPHeaderField: 
              "Content-Type")
    return requestObservable.callAPI(request: request)
  }
  
  func sendPost(recipeModel: PostModel) -> Observable<PostModel> {
     var request = URLRequest(url:  
          URL(string:"https://jsonplaceholder.typicode.com/posts")!)
     request.httpMethod = "POST"
     request.addValue("application/json", forHTTPHeaderField: 
      "Content-Type")
     let payloadData = try? JSONSerialization.data(withJSONObject:   
           recipeModel.dictionaryValue!, options: [])
     request.httpBody = payloadData
     return requestObservable.callAPI(request: request)
   }
}

5. Use WebAPI singleton in viewcontroller##

And finally using our singleton with observable in our controller. Here we subscribe to our observable and wait for emitted events.

import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
  var posts: [PostModel] = []
  let disposeBag = DisposeBag()
  override func viewDidLoad() {
    super.viewDidLoad()
    
    let client = APIClient.shared
    do{
      try client.getRecipes().subscribe(
        onNext: { result in
           self.posts = result
           //MARK: display in UITableView
        },
        onError: { error in
           print(error.localizedDescription)
        },
        onCompleted: {
           print("Completed event.")
        }).disposed(by: disposeBag)
      }
      catch{
    }
  }
}

Thank you for reading this far. Please clap your hands and visit my website:

Code is available on github:

Conact me at:

begic_kenan@hotmail.com
info@redundantcoding.com
www.linkedin.com/in/kenan-begić-05733361

Discover and read more posts from Kenan Begić
get started