• Stars
    star
    538
  • Rank 82,538 (Top 2 %)
  • Language
    Swift
  • License
    MIT License
  • Created almost 9 years ago
  • Updated 10 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Lightweight network abstraction layer, written on top of Alamofire

CI codecov.io CocoaPod platform CocoaPod version Swift Package Manager compatible Packagist

TRON is a lightweight network abstraction layer, built on top of Alamofire. It can be used to dramatically simplify interacting with RESTful JSON web-services.

Features

  • Generic, protocol-based implementation
  • Built-in response and error parsing
  • Support for any custom mapper (SwiftyJSON implementation provided). Defaults to Codable protocol.
  • Support for upload tasks
  • Support for download tasks and resuming downloads
  • Robust plugin system
  • Stubbing of network requests
  • Modular architecture
  • Support for iOS/Mac OS X/tvOS/watchOS/Linux
  • Support for CocoaPods/Swift Package Manager
  • RxSwift / Combine extensions
  • Support for Swift Concurrency
  • Complete documentation

Overview

We designed TRON to be simple to use and also very easy to customize. After initial setup, using TRON is very straightforward:

let request: APIRequest<User,APIError> = tron.codable.request("me")
request.perform(withSuccess: { user in
  print("Received User: \(user)")
}, failure: { error in
  print("User request failed, parsed error: \(error)")
})

Requirements

  • Xcode 13 and higher
  • Swift 5.3 and higher
  • iOS 11 / macOS 10.13 / tvOS 11.0 / watchOS 4.0

Installation

Swift Package Manager

  • Add package into Project settings -> Swift Packages

TRON framework includes Codable implementation. To use SwiftyJSON, import TRONSwiftyJSON framework. To use RxSwift wrapper, import RxTRON.

CocoaPods

pod 'TRON', '~> 5.3.0'

Only Core subspec, without SwiftyJSON dependency:

pod 'TRON/Core', '~> 5.3.0'

RxSwift extension for TRON:

pod 'TRON/RxSwift', '~> 5.3.0'

Migration Guides

Project status

TRON is under active development by MLSDev Inc. Pull requests are welcome!

Request building

TRON object serves as initial configurator for APIRequest, setting all base values and configuring to use with baseURL.

let tron = TRON(baseURL: "https://api.myapp.com/")

You need to keep strong reference to TRON object, because it holds Alamofire.Manager, that is running all requests.

URLBuildable

URLBuildable protocol is used to convert relative path to URL, that will be used by request.

public protocol URLBuildable {
    func url(forPath path: String) -> URL
}

By default, TRON uses URLBuilder class, that simply appends relative path to base URL, which is sufficient in most cases. You can customize url building process globally by changing urlBuilder property on TRON or locally, for a single request by modifying urlBuilder property on APIRequest.

Sending requests

To send APIRequest, call perform(withSuccess:failure:) method on APIRequest:

let alamofireRequest = request.perform(withSuccess: { result in }, failure: { error in})

Alternatively, you can use performCollectingTimeline(withCompletion:) method that contains Alamofire.Response inside completion closure:

request.performCollectingTimeline(withCompletion: { response in
    print(response.timeline)
    print(response.result)
})

In both cases, you can additionally chain Alamofire.Request methods, if you need:

request.perform(withSuccess: { result in }, failure: { error in }).progress { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
    print(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
}

Response parsing

Generic APIRequest implementation allows us to define expected response type before request is even sent. On top of Alamofire DataResponseSerializerProtocol, we are adding one additional protocol for error-handling.

public protocol DataResponseSerializerProtocol {
    associatedtype SerializedObject

    public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Self.SerializedObject
}

public protocol ErrorSerializable: Error {
    init?(serializedObject: Any?, request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?)
}

Codable

Parsing models using Swift4 Codable protocol is simple, implement Codable protocol:

struct User: Codable {
  let name : String
  let id: Int
}

And send a request:

let request: APIRequest<User,APIError> = tron.codable.request("me")
request.perform(withSuccess: { user in
  print("Received user: \(user.name) with id: \(user.id)")
})

It's possible to customize decoders for both model and error parsing:

let userDecoder = JSONDecoder()

let request : APIRequest<User,APIError> = tron.codable(modelDecoder: userDecoder).request("me")

JSONDecodable

TRON provides JSONDecodable protocol, that allows us to parse models using SwiftyJSON:

public protocol JSONDecodable {
    init(json: JSON) throws
}

To parse your response from the server using SwiftyJSON, all you need to do is to create JSONDecodable conforming type, for example:

class User: JSONDecodable {
  let name : String
  let id: Int

  required init(json: JSON) {
    name = json["name"].stringValue
    id = json["id"].intValue
  }
}

And send a request:

let request: APIRequest<User,MyAppError> = tron.swiftyJSON.request("me")
request.perform(withSuccess: { user in
  print("Received user: \(user.name) with id: \(user.id)")
})

There are also default implementations of JSONDecodable protocol for Swift built-in types like String, Int, Float, Double and Bool, so you can easily do something like this:

let request : APIRequest<String,APIError> = tron.swiftyJSON.request("status")
request.perform(withSuccess: { status in
    print("Server status: \(status)") //
})

You can also use Alamofire.Empty struct in cases where you don't care about actual response.

Some concepts for response serialization, including array response serializer, are described in Container Types Parsing document

It's possible to customize JSONSerialization.ReadingOptions, that are used by SwiftyJSON.JSON object while parsing data of the response:

let request : APIRequest<String, APIError> = tron.swiftyJSON(readingOptions: .allowFragments).request("status")

Swift Concurrency

Sending requests using Swift Concurrency is done via a proxy object RequestSender(or DownloadRequestSender for download requests). Simple usage example:

let request : APIRequest<User, APIError> = tron.codable.request("/me")
do {
 let user = try await request.sender().value
  // user variable contains User type
} catch {
  // Network request failed
}

If you prefer to receive result, containing either successful Model, or ErrorModel, you can do that too:

let request : APIRequest<User, APIError> = tron.codable.request("/me")
let result = await request.sender().result
// result is Result<User,APIError>

There is also response async property, containing all request information, if you need it:

let request : APIRequest<User, APIError> = tron.codable.request("/me")
let response = await request.sender().response
// response: AFDataResponse<Model>

Upload request

For upload requests, it's useful to monitor upload progress, and show it to the user:

let request : APIRequest<User, APIError> = tron.codable.request("/me/profile_picture")
  .upload("/post", fromFileAt: urlForResource("cat", withExtension: "jpg"))
  .method(.post)  

let sender = request.sender()
Task {
    for await progress in sender.uploadProgress {
      // Update progress view, progress: Progress
    }
}
let result = await sender.result

Download request

Similarly to upload requests, download requests have downloadProgress property implemented as async sequence:

Task {
    for await progress in sender.downloadProgress {
      // Update download view, progress: Progress
    }
}

If you only care about downloaded file URL, and not parsed data model, you can await responseURL property on request sender:

let destination = Alamofire.DownloadRequest.suggestedDownloadDestination()
let request: DownloadAPIRequest<URL, APIError> = tron
            .download("/download",
                      to: destination,
                      responseSerializer: FileURLPassthroughResponseSerializer())
do {
  let fileURL = try await request.sender().responseURL 
} catch {
  // Handle error
}

RxSwift

let request : APIRequest<Foo, APIError> = tron.codable.request("foo")
_ = request.rxResult().subscribe(onNext: { result in
    print(result)
})
let multipartRequest : UploadAPIRequest<Foo,APIError> = tron.codable.uploadMultipart("foo", formData: { _ in })
multipartRequest.rxResult().subscribe(onNext: { result in
    print(result)
})

Error handling

TRON includes built-in parsing for errors. APIError is an implementation of ErrorSerializable protocol, that includes several useful properties, that can be fetched from unsuccessful request:

request.perform(withSuccess: { response in }, failure: { error in
    print(error.request) // Original URLRequest
    print(error.response) // HTTPURLResponse
    print(error.data) // Data of response
    print(error.fileURL) // Downloaded file url, if this was a download request
    print(error.error) // Error from Foundation Loading system
    print(error.serializedObject) // Object that was serialized from network response
  })

CRUD

struct Users
{
    static let tron = TRON(baseURL: "https://api.myapp.com")

    static func create() -> APIRequest<User,APIError> {
      tron.codable.request("users").post()
    }

    static func read(id: Int) -> APIRequest<User, APIError> {
        tron.codable.request("users/\(id)")
    }

    static func update(id: Int, parameters: [String:Any]) -> APIRequest<User, APIError> {
      tron.codable.request("users/\(id)").put().parameters(parameters)
    }

    static func delete(id: Int) -> APIRequest<User,APIError> {
      tron.codable.request("users/\(id)").delete()
    }
}

Using these requests is really simple:

Users.read(56).perform(withSuccess: { user in
  print("received user id 56 with name: \(user.name)")
})

It can be also nice to introduce namespacing to your API:

enum API {}
extension API {
  enum Users {
    // ...
  }
}

This way you can call your API methods like so:

API.Users.delete(56).perform(withSuccess: { user in
  print("user \(user) deleted")
})

Stubbing

Stubbing is built right into APIRequest itself. All you need to stub a successful request is to set apiStub property and turn stubbingEnabled on:

API.Users.get(56)
         .stub(with: APIStub(data: User.fixture().asData))
         .perform(withSuccess: { stubbedUser in
           print("received stubbed User model: \(stubbedUser)")
})

Stubbing can be enabled globally on TRON object or locally for a single APIRequest. Stubbing unsuccessful requests is easy as well:

API.Users.get(56)
         .stub(with: APIStub(error: CustomError()))
         .perform(withSuccess: { _ in },
                  failure: { error in
  print("received stubbed api error")
})

You can also optionally delay stubbing time:

request.apiStub.stubDelay = 1.5

Upload

  • From file:
let request = tron.codable.upload("photo", fromFileAt: fileUrl)
  • Data:
let request = tron.codable.upload("photo", data: data)
  • Stream:
let request = tron.codable.upload("photo", fromStream: stream)
  • Multipart-form data:
let request: UploadAPIRequest<EmptyResponse,MyAppError> = tron.codable.uploadMultipart("form") { formData in
    formData.append(data, withName: "cat", mimeType: "image/jpeg")
}
request.perform(withSuccess: { result in
    print("form sent successfully")
})

Download

let responseSerializer = TRONDownloadResponseSerializer { _,_, url,_ in url }
let request: DownloadAPIRequest<URL?, APIError> = tron.download("file",
                                                                to: destination,
                                                                responseSerializer: responseSerializer)

Plugins

TRON includes plugin system, that allows reacting to most of request events.

Plugins can be used globally, on TRON instance itself, or locally, on concrete APIRequest. Keep in mind, that plugins that are added to TRON instance, will be called for each request. There are some really cool use-cases for global and local plugins.

By default, no plugins are used, however two plugins are implemented as a part of TRON framework.

NetworkActivityPlugin

NetworkActivityPlugin serves to monitor requests and control network activity indicator in iPhone status bar. This plugin assumes you have only one TRON instance in your application.

let tron = TRON(baseURL: "https://api.myapp.com", plugins: [NetworkActivityPlugin()])

NetworkLoggerPlugin

NetworkLoggerPlugin is used to log responses to console in readable format. By default, it prints only failed requests, skipping requests that were successful.

Local plugins

There are some very cool concepts for local plugins, some of them are described in dedicated PluginConcepts page.

Alternatives

We are dedicated to building best possible tool for interacting with RESTful web-services. However, we understand, that every tool has it's purpose, and therefore it's always useful to know, what other tools can be used to achieve the same goal.

TRON was heavily inspired by Moya framework and LevelUPSDK, which is no longer available in open-source.

License

TRON is released under the MIT license. See LICENSE for details.

About MLSDev

MLSDev.com

TRON is maintained by MLSDev, Inc. We specialize in providing all-in-one solution in mobile and web development. Our team follows Lean principles and works according to agile methodologies to deliver the best results reducing the budget for development and its timeline.

Find out more here and don't hesitate to contact us!

More Repositories

1

RxImagePicker

Android. Pick image from camera or gallery using RxJava2
Kotlin
334
star
2

AnimatedRecyclerView

RecyclerView with layout animations
Java
280
star
3

development-standards

MLSDev Inc. Development Standards
55
star
4

example-realm-android

Java
51
star
5

LoadableViews

Easiest way to load view classes into another XIB or storyboard.
Swift
41
star
6

DroidFM

This application shows how you can integrate the RxJava, Realm, LastFM API, VK API for information on popular artists, their songs and albums. Additionally this app can stream and download any tracks that are available in VK.
Java
24
star
7

E-commerce-API

Ruby
19
star
8

RecipeFinderJavaVersion

Java
10
star
9

RxKeyboard

Java
9
star
10

MapsAndroidAppSample

It is a sample app with basic google maps functionality.
Kotlin
6
star
11

ecto_extensions

Useful Ecto extensions: search, sort, paginate, validators
Elixir
6
star
12

VideoThumbnailExtractor

Pick image frame from video using LiveData component
Kotlin
6
star
13

android-widget-SpreadBar

SpreadBar widget for Urs
Java
4
star
14

rails-chats-api

Chats API - test application
Ruby
4
star
15

activeadmin-map_address

Ruby
4
star
16

mls_ruby_capistrano_slacker

The way to organize your deploys using GitLab and Capistrano
Ruby
3
star
17

api_session_recovering

Recover json api session.
Ruby
3
star
18

easy-matchers

Easy matchers provides RSpec matchers for common Rails functionality
Ruby
3
star
19

MovieViewer

Sample app for exploring movies, TV and celebrities
Kotlin
3
star
20

the_bullet

Rails application template for API-only applications
Ruby
3
star
21

elixir-workshop-01

Internal MLSDev Elixir Workshop #01 - Automated Slack reminder
Elixir
3
star
22

generator-waab

Angular 2 and Angular 4 application generator based on webpack bundling system
JavaScript
3
star
23

api_authentication

json api authentication
Ruby
2
star
24

easy-demo

Demo Ruby on Rails application for easy-matchers
Ruby
2
star
25

elixir-workshop-05

Internal MLSDev Elixir Workshop #05 - Authorization
Elixir
2
star
26

mls_ruby_automated_gitlab_tags

Automated tool that prepares and creates GitLab release with notes
Ruby
2
star
27

LiveDataSocialAuth

Android LiveData library for Social auth with Google and Facebook
Kotlin
2
star
28

dinero

Elixir
2
star
29

AuthorizationAndroidAppSample

Java
1
star
30

elixir-workshop-03

Internal MLSDev Elixir Workshop #03 - Authentication
Elixir
1
star
31

elixir-workshop-07

Internal MLSDev Elixir Workshop #07 - Umbrella apps, part 1
Elixir
1
star
32

ConnectivityLiveData

The application network state observation
Kotlin
1
star
33

homie

Ruby
1
star
34

Android-Studio-templates

Code templates for Android studio
FreeMarker
1
star
35

AnimationsPack

Another implementation of animate.css on Android
Kotlin
1
star
36

mls_rubocop_configs

The place to make the world better πŸ€―πŸ‘Œ
1
star
37

rswag_schema_export

Export/Import your rswag schema.json during deploy with CI
Ruby
1
star
38

crystal_amber_backend_example

Crystal
1
star
39

the_bullet-generator

Ruby
1
star
40

elixir-workshop-04

Internal MLSDev Elixir Workshop #04 - Authentication, part 2
Elixir
1
star
41

moonstones

name was generated using `crystal init lib $(curl -s crystalshards.xyz/name)`
Crystal
1
star
42

elixir-workshop-06

Internal MLSDev Elixir Workshop #06 - Chat
Elixir
1
star
43

RecipeFinderKotlinVersion

Kotlin
1
star
44

mlsdev-frontend-interview-task

TypeScript
1
star
45

modules-templates

TypeScript
1
star