SkillAgentSearch skills...

Opera

Protocol-Oriented Network abstraction layer written in Swift.

Install / Use

/learn @xmartlabs/Opera
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

OperaSwift

<p align="left"> <a href="https://travis-ci.org/xmartlabs/Opera"><img src="https://travis-ci.org/xmartlabs/Opera.svg?branch=master" alt="Build status" /></a> <img src="https://img.shields.io/badge/platform-iOS%20|%20OSX%20|%20watchOS%20|%20tvOS-blue.svg?style=flat" alt="Platform iOS" /> <a href="https://developer.apple.com/swift"><img src="https://img.shields.io/badge/swift4-compatible-4BC51D.svg?style=flat" alt="Swift 4 compatible" /></a> <a href="https://github.com/Carthage/Carthage"><img src="https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat" alt="Carthage compatible" /></a> <a href="https://cocoapods.org/pods/OperaSwift"><img src="https://img.shields.io/cocoapods/v/OperaSwift.svg" alt="CocoaPods compatible" /></a> <a href="https://raw.githubusercontent.com/xmartlabs/Opera/master/LICENSE"><img src="http://img.shields.io/badge/license-MIT-blue.svg?style=flat" alt="License: MIT" /></a> </p>

Made with ❤️ by XMARTLABS. View all our open source contributions.

Introduction

Protocol-Oriented Network abstraction layer written in Swift. Greatly inspired by RxPagination project but working on top of Alamofire and the JSON parsing library of your choice.

Features

  • API abstraction through RouteType conformance.
  • Pagination support through PaginationRequestType conformance.
  • Supports for any JSON parsing library such as Decodable and Argo through OperaDecodable protocol conformance.
  • Networking errors abstraction through OperaError type. OperaSwift OperaError indicates either an NSURLSession error, Alamofire error, or your JSON parsing library error.
  • RxSwift wrappers around Alamofire.Request that return either a Single of a JSON serialized type or an array if it or a completable sequence. NetworkError is passed when error event happens.
  • RxSwift wrappers around PaginationRequestType that return a Single of a PaginationResponseType which contains the serialized elements and information about the current, next and previous page.
  • Ability to easily mock services through RouteType.sampleData.
  • Ability to use multiple RequestAdapters through CompositeAdapter.
  • Easily upload files or images using HTTP multipart requests.
  • Download progress on every RouteType and upload progress on MultipartRouteType.

Usage

Route setup

A RouteType is a high level representation of the request for a REST API endpoint. By adopting the RouteType protocol a type is able to create its corresponding request.


import Alamofire
import OperaSwift

// just a hierarchy structure to organize routes
struct GithubAPI {
    struct Repository {}
}

extension GithubAPI.Repository {

  struct Search: RouteType {

      var method: HTTPMethod { return .get }
      var path: String { return "search/repositories" }
  }

  struct GetInfo: RouteType {

      let owner: String
      let repo: String

      var method: HTTPMethod { return .get }
      var path: String { return "repos/\(owner)/\(repo)" }
  }
}

Alternatively, you can opt to conform to RouteType form an enum where each enum value is a specific route (api endpoint) with its own associated values.

If you are curious check out the rest of RouteType protocol definition.

As you may have seen, any type that conforms to RouteType must provide baseUrl and the Alamofire manager instance.

Usually these values do not change among our routes so we can provide them by implementing a protocol extension over RouteType as shown below.

extension RouteType {

    var baseURL: URL {
        return URL(string: "https://api.github.com")!
    }

    var manager: ManagerType {
        return Manager.singleton
    }
}

Now, by default, all RouteTypes we define will provide https://api.github.com as baseUrl and Manager.singleton as mananger. It's up to you to customize it within a specific RouteType protocol conformance.

Default RouteTypes

To avoid having to implement the method property in every RouteType Opera provides A protocol for each HTTPMethod so you can implement those:

protocol GetRouteType: RouteType {}
protocol PostRouteType: RouteType {}
protocol OptionsRouteType: RouteType {}
protocol HeadRouteType: RouteType {}
protocol PutRouteType: RouteType {}
protocol PatchRouteType: RouteType {}
protocol DeleteRouteType: RouteType {}
protocol TraceRouteType: RouteType {}
protocol ConnectRouteType: RouteType {}

They are pretty simple, they only implement the method property of RouteType with the HTTPMethod that matches.

Additional RouteTypes

ImageUploadRouteType

struct Upload: ImageUploadRouteType {

  let image: UIImage
  let encoding: ImageUploadEncoding = .jpeg(quality: 0.80)
  let path = "/upload"
  let baseURL = URL(string: "...")!

}

And then use it like this:

Upload(image: UIImage(named: "myImage")!)
  .rx
  .completable()
  .subscribe(
    onCompleted: {
      // success :)
    },
    onError: { error in
      // do something when something went wrong
    }
  )
  .addDisposableTo(disposeBag)

Note: If you want to upload a generic list of files through an HTTP multipart request, use MultipartRouteType instead.

Creating requests

At this point we can easily create an Alamofire Request:

let request: Request =  GithubAPI.Repository.GetInfo(owner: "xmartlabs", repo: "Opera").request

Notice that RouteType conforms to Alamofire.URLConvertible so having the manager we can create the associated Request.

We can also take advantage of the reactive helpers provided by Opera:

request
  .rx.collection()
  .subscribe(
    onNext: { (repositories: [Repository]) in
      // do something when networking and Json parsing completes successfully
    },
    onError: {(error: Error) in
      // do something when something went wrong
    }
  )
  .addDisposableTo(disposeBag)
getInfoRequest
  .rx.collection()
  .subscribe(
    onSuccess: { (repositories: [Repository]) in
      // do something when networking and Json parsing completes successfully
    },
    onError: {(error: Error) in
      guard let error = error as? OperaError else {
          //do something when it's not an OperaError
      }
      // do something with the OperaError
    }
  )
  .addDisposableTo(disposeBag)

If you are not interested in decode your JSON response into a Model you can invoke request.rx.any() which returns an Single of Any for the current request and propagates a OperaError error through the result sequence if something goes wrong.

Error Handling

If you are using the reactive helpers (which are awesome btw!) you can handle the errors on the onError callback which returns an Error that, in case of Networking or Parsing issues, can be casted to OperaError for easier usage. OperaError wraps any error that is Networking or Parsing related. Keep in mind that you have to cast the Error on the onError callback before using it. OperaError also provides a set of properties that make accessing the error's data easier:

    public var error: Error
    public var request: URLRequest?
    public var response: HTTPURLResponse?
    public var body: Any?
    public var statusCode: Int?
    public var localizedDescription: String

Example:

getInfoRequest
  .rx.object()
  .subscribe(
    onError: {(error: Error) in
      guard let error = error as? OperaError else {
          //do something when it's not an OperaError
      }
      // do something with the OperaError
      debugPrint("Request failed with status code \(error.statusCode)")
    }
  )
  .addDisposableTo(disposeBag)

Download & Upload progress

Every RouteType can optionally chain a download progress handler through its reactive extension:

let request: RouteType = ...
request
  .rx.collection()
  .downloadProgress {
    debugPrint("Download progress: \($0.fractionCompleted)")
  }
  .subscribe(
    onNext: { (repositories: [Repository]) in
      // do something when networking and Json parsing completes successfully
    },
    onError: {(error: Error) in
      // do something when something went wrong
    }
  )
  .addDisposableTo(disposeBag)

Only if the routeType is a MultipartRouteType we can also chain an upload progress handler:

ImageUploadRouteType is a specific MultipartRouteType to easily upload images.

  let imageUpload: ImageUploadRouteType = ...
  imageUpload
    .rx
    .uploadProgress {
      debugPrint("Upload progress: \($0.fractionCompleted)")
    }
    .downloadProgress {
      debugPrint("Download progress: \($0.fractionCompleted)")
    }
    .completable()
    .subscribe(
      onCompleted: {
        debugPrint("Completed")
      },
      onError: { error in
        ...
      }
    )
    .addDisposableTo(disposeBag)

Decoding

We've said Opera is able to decode JSON response into a Model using your favorite JSON parsing library. Let's see how Opera accomplishes that.

At Xmartlabs we have been using Decodable as our JSON parsing library since March 16. Before that we had used Argo, ObjectMapper and many others. I don't want to deep into the reason of our JSON parsing library choice (we do have our reasons ;)) but during Opera implementation/design we thought it was a good feature to be flexible about it.

This is our Repository model...

struct Repository {

    let id: Int
    let name: String
    let desc: String?
    let company: String?
    let language: String?
    let openIssues: Int
    let stargazersCount: Int
    let forksCount: Int
    let url: NSURL
    let createdAt: NSDate

}

and OperaDecodable

View on GitHub
GitHub Stars77
CategoryDevelopment
Updated8mo ago
Forks9

Languages

Swift

Security Score

87/100

Audited on Aug 5, 2025

No findings