A configurable api client based on Alamofire4 and RxSwift4 for iOS

Overview

SimpleApiClient

Swift CI Status Version License Platform

A configurable api client based on Alamofire4 and RxSwift4 for iOS

Requirements

  • iOS 8.0+
  • Swift 4

Table of Contents

Installation

SimpleApiClient is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'SimpleApiClient'

Basic Usage

Step 1

Configurate the api client

let config = SimpleApiClient.Config(
  baseUrl: "https://api.github.com",
  defaultParameters: ["foo": "bar"],
  defaultHeaders: ["foo": "bar"],
  timeout: 120, // default is 60s
  certificatePins: [
    CertificatePin(hostname: "https://api.github.com", certificateUrl: Bundle.main.url(forResource: "serverCert", withExtension: "cer")!)
  ],
  errorMessageKeyPath: "message",
  jsonDecoder: JSONDecoder(),  // default is JSONDecoder()
  isMockResponseEnabled: true, // default is false
  logHandler: { request, response in
    ...
  },
  errorHandler: { error in
    // you can centralize the handling of general error here
    switch error {
    case .authenticationError(let code, let message):
      ...
    case .clientError(let code, let message):
      ...
    case .serverError(let code, let message):
      ...
    case .networkError(let source):
      ...
    case .sslError(let source):
      ...
    case .uncategorizedError(let source):
      ...
    }
  }
)

let githubClient = SimpleApiClient(config: config)

Step 2

Create the API

import SimpleApiClient

struct GetRepoApi: SimpleApi {
  let user: String
  let repo: String
  
  var path: String {
    return "/repos/\(user)/\(repo)"
  }
  
  var method: HTTPMethod {
    return .get
  }
  
  // optional
  var parameters: Parameters {
    return [:]
  }
  
  // optional
  var headers: HTTPHeaders {
    return [:]
  }
}

extension SimpleApiClient {
  func getRepo(user: String, repo: String) -> Observable<Repo> {
    return request(api: GetRepoApi(user: user, repo: repo))
  }
}

Step 3

Use observe() to enqueue the call, do your stuff in corresponding parameter block. All blocks run on main thread by default and are optional.

githubClient.getRepo(user: "foo", repo: "bar")
  .observe(
    onStart: { print("show loading") },
    onEnd: { print("hide loading") },
    onSuccess: { print("sucess: \($0)") },
    onError: { print("error: \($0)" }
  )

Model

The library uses JSONDecoder to deserialize JSON to model object, so the model should conform to Decodable or Codable

struct User: Decodable {
  let name: String
  let profileUrl: URL
  // if the serialized name is different from the property name
  private enum CodingKeys: String, CodingKey {
    case name = "login"
    case profileUrl = "avatar_url"
  }
}

Unwrap Response by KeyPath

Sometimes the api response includes metadata that we don't need, but in order to map the response we create a wrapper class and make the function return that wrapper class. This approach leaks the implementation of service to calling code.

Assuming the response json looks like the following:

{
  total_count: 33909,
  incomplete_results: false,
  foo: {
    bar: {
      items: [
        {
          login: "foo",
          ...
        }
        ...
      ]
    }
  }
}

And you only need the items part, implement Unwrappable to indicate which part of response you want.

struct GetUsersApi: SimpleApi, Unwrappable {
  ... 
  
  var responseKeyPath: String {
    return "foo.bar.items"
  }
}

// then your response will be a list of User
extension SimpleApiClient {
  func getUsers(query: String) -> Observable<[User]> {
    return request(api: GetUsersApi(query: query))
  }
}

Upload File(s)

To upload file(s), make the API implements Uploadable to provide Multiparts

struct UploadImageApi: SimpleApi, Uploadable {
  ...
  
  var multiParts: [MultiPart] {
    let multiPart = MultiPart(data: UIImageJPEGRepresentation(image, 1)!, name: "imagefile", filename: "image.jpg", mimeType: "image/jpeg")
    return [multiPart]
  }
}

extension SimpleApiClient {
  func uploadImage(image: UIImage) -> Observable<Image> {
    return request(api: UploadImageApi(image))
  }
}

Serial / Parallel Calls

Serial

githubClient.foo()
  .then { foo in githubClient.bar(foo.name) }
  .observe(...)

Serial then Parallel

githubClient.foo()
  .then { foo in githubClient.bar(foo.name) }
  .thenAll { bar in 
    (githubClient.baz(bar.name), githubClient.qux(bar.name)) // return a tuple
  }
  .observe(...)

Parallel

SimpleApiClient.all(
  githubApi.foo(),
  githubApi.bar()
)
.observe(...)

Parallel then Serial

SimpleApiClient.all(
  githubApi.foo(),
  githubApi.bar()
).then { array -> // the return type is Array<Any>, you should cast them, e.g. let foo = array[0] as! Foo
  githubApi.baz()
}.observe(...)

Retry Interval / Exponential backoff

githubClient.getUsers("foo")
  .retry(delay: 5, maxRetryCount: 3) // retry up to 3 times, each time delays 5 seconds
  .retry(exponentialDelay: 5, maxRetryCount: 3) // retry up to 3 times, each time delays 5^n seconds, where n = {1,2,3}
  .observe(...)

Call Cancellation

Auto Call Cancellation

The call will be cancelled when the object is deallocated.

githubClient.getUsers("foo")
  .cancel(when: self.rx.deallocated)
  .observe(...)

Cancel call manually

let call = githubClient.getUsers("foo").observe(...)

call.cancel()

Mock Response

To enable response mocking, set SimpleApiClient.Config.isMockResponseEnabled to true and make the API implements Mockable to provide MockResponse.

Mock sample json data

To make the api return a successful response with provided json

struct GetUsersApi: SimpleApi, Mockable {
  ...
  
  var mockResponse: MockResponse {
    let file = Bundle.main.url(forResource: "get_users", withExtension: "json")!
    return MockResponse(jsonFile: file)
  }
}

Mock status

To make the api return a client side error with provided json

struct GetUsersApi: SimpleApi, Mockable {
  ...
  
  var mockResponse: MockResponse {
    let file = Bundle.main.url(forResource: "get_users_error", withExtension: "json")!
    return MockResponse(jsonFile: file, status: .clientError)
  }
}

the parameter jsonFile of MockResponse is optional, you can set the status only, then you receive empty string.

Possible Status values:

public enum Status {
  case success
  case authenticationError
  case clientError
  case serverError
  case networkError
  case sslError
}

To mock a response with success status only, you should return Observable<Nothing>.

struct DeleteRepoApi: SimpleApi, Mockable {
  ...
  
  var mockResponse: MockResponse {
    return MockResponse(status: .success)
  }
}

extension SimpleApiClient {
  func deleteRepo(id: String) -> Observable<Nothing> {
    return request(api: DeleteRepoApi(id: id))
  }
}

Author

jaychang0917, [email protected]

License

SimpleApiClient is available under the MIT license. See the LICENSE file for more info.

You might also like...
🤖 RxSwift + State Machine, inspired by Redux and Elm.
🤖 RxSwift + State Machine, inspired by Redux and Elm.

RxAutomaton RxSwift port of ReactiveAutomaton (State Machine). Terminology Whenever the word "signal" or "(signal) producer" appears (derived from Rea

Simple and lightweight Functional Reactive Coding in Swift for the rest of us
Simple and lightweight Functional Reactive Coding in Swift for the rest of us

The simplest ObservableT implementation for Functional Reactive Programming you will ever find. This library does not use the term FRP (Functional R

Lightweight observations and bindings in Swift
Lightweight observations and bindings in Swift

What is Hanson? Hanson is a simple, lightweight library to observe and bind values in Swift. It's been developed to support the MVVM architecture in o

Unidirectional State Management Architecture for Swift - Inspired by Vuex and Flux
Unidirectional State Management Architecture for Swift - Inspired by Vuex and Flux

Unidirectional State Management Architecture for Swift - Inspired by Vuex and Flux Introduction VueFlux is the architecture to manage state with unidi

STDevRxExt contains some extension functions for RxSwift and RxCocoa which makes our live easy.

STDevRxExt Example To run the Example.playground, clone the repo, and run pod install from the Example directory first. Requirements iOS 9.0+ tvOS 9.0

CMPSC475 Final Project, ArboretumID Application allows users to explore the Penn State Arboretum, identify plants and learn about the exhibits!

ArboretumID: CMPSC475 Final Project Taylan Unal (@taylanu) About ArboretumID ArboretumIdentifier (ArboretumID) is an app that enhances the Penn State

Binding - Data binding framework (view model binding on MVVM) written using propertyWrapper and resultBuilder

Binding Data binding framework (view model binding on MVVM) written using @prope

UIKitPreviews - A simple way to see your UIKit ViewController changes live and on-demand
UIKitPreviews - A simple way to see your UIKit ViewController changes live and on-demand

SwiftUI preview provider for UIKit A simple way to see your UIKit ViewController

KVO for Swift - Value Observing and Events

Value Observing and Events for Swift Swift lacks the powerful Key Value Observing (KVO) from Objective-C. But thanks to closures, generics and propert

Owner
Jay
Jay
🎌 Powerful navigation library for iOS based on the coordinator pattern

⚠️ We have recently released XCoordinator 2.0. Make sure to read this section before migrating. In general, please replace all AnyRouter by either Uno

QuickBird Studios 2k Dec 30, 2022
RxAlamoRecord combines the power of the AlamoRecord and RxSwift libraries to create a networking layer that makes interacting with API's easier than ever reactively.

Written in Swift 5 RxAlamoRecord combines the power of the AlamoRecord and RxSwift libraries to create a networking layer that makes interacting with

Dalton Hinterscher 9 Aug 7, 2020
RxSwift bindings for Permissions API in iOS.

RxPermission RxSwift bindings for Permission API that helps you with Permissions in iOS. Installation RxPermission is available through CocoaPods. I c

Luke 230 Dec 27, 2022
Two-way data binding framework for iOS. Only one API to learn.

BindKit A simple to use two-way data binding framework for iOS. Only one API to learn. Supports Objective-C, Swift 5, Xcode 10.2, iOS 8 and above. Shi

Electric Bolt 13 May 25, 2022
Flexible, stream-based abstraction for launching processes

ReactiveTask ReactiveTask is a Swift framework for launching shell tasks (processes), built using ReactiveSwift. let strings = [ "foo\n", "bar\n", "bu

Carthage 131 Nov 5, 2022
Dynamic and type-safe framework for building linear and non-linear flows.

FlowKit FlowKit is a dynamic flow framework capable of building a flow, based on conditions and ordered according to a logic of next steps. By using F

N26 55 Dec 20, 2022
🟣 Verge is a very tunable state-management engine on iOS App (UIKit / SwiftUI) and built-in ORM.

Verge.swift ?? An effective state management architecture for iOS - UIKit and also SwiftUI ?? _ An easier way to get unidirectional data flow _ _ Supp

VergeGroup 478 Dec 29, 2022
A powerful, minimal and composable architecture for building reactive iOS apps with SwiftUI or UIKit

SourceArchitecture A simple yet powerful framework for reactive programming with only a minimal optimized set of types. Sources are self-contained, hi

Daniel Hall 6 Nov 1, 2022
Cocoa framework and Obj-C dynamism bindings for ReactiveSwift.

Reactive extensions to Cocoa frameworks, built on top of ReactiveSwift. ⚠️ Looking for the Objective-C API? ?? Migrating from RAC 4.x? ?? Release Road

null 20k Jan 8, 2023
RxSwift extentions for Swift optionals and "Occupiable" types

RxOptional RxSwift extentions for Swift optionals and "Occupiable" types. Usage All operators are available on Driver as well unless otherwise marked.

Thane Gill 8 Jun 28, 2020