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:
Conact me at:
begic_kenan@hotmail.com
info@redundantcoding.com
www.linkedin.com/in/kenan-begić-05733361