Swiftpack.co - Package - MLSDev/TRON

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 extension
  • 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 10 and higher
  • Swift 4 and higher
  • iOS 10 / macOS 10.12 / tvOS 10.0 / watchOS 3.0

Installation

Swift Package Manager(requires Xcode 11)

  • 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.0.0'

Only Core subspec, without SwiftyJSON dependency:

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

RxSwift extension for TRON:

pod 'TRON/RxSwift', '~> 5.0.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")

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> {
      return tron.codable.request("users").post()
    }

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

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

    static func delete(id: Int) -> APIRequest<User,APIError> {
      return 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

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!

Github

link
Stars: 503

Used By

Total: 0

Releases

5.0.2 - 2020-03-19 09:00:00

  • Added DownloadAPIRequest.perform(withSuccess:failure:) method, similar to APIRequest and UploadAPIRequest methods.

5.0.1 - 2020-03-10 15:24:33

  • BaseRequest.headers now default to HTTPHeaders.default to allow sending Alamofire default headers such as "Accept-Language", "Accept-Encoding", "User-Agent".

5.0.0 - 2020-02-17 09:35:19

5.0.0-rc.1 - 2019-09-06 16:08:47

Added

  • Better debug prints for NetworkLoggerPlugin when decoding using Codable protocol.
  • configure(_:) method for BaseRequest DSL to allow configuring request with several changes at once.

Breaking

  • Plugin API that previously was called with Alamofire.Data/DownloadResponse<Model, Error> is now called with Alamofire.Data/DownloadResponse<Model, AFError> due to Alamofire changes to error handling.
  • performCollectingTimeline(withCompletion:) method is now called also with AFError instead of Error.

5.0.0-beta.5 - 2019-06-28 13:23:20

Added

  • Support for per-request Interceptors.
  • Three different behaviors for building URLs: .appendingPathComponent, .relativeToBaseURL and .custom. Those can be set in TRON initializer:
let tron = TRON(baseURL: "https://www.example.com/", buildingURL: .relativeToBaseURL)

Or you can change URLBuilder.Behavior on per-request basis, using the new DSL:

let request: APIRequest<Int,APIError> = tron.swiftyJSON
    .request("status/200")
    .buildURL(.relativeToBaseURL)

Default behavior for TRON is .appendingPathComponent.

Removed

  • URLBuildable protocol. Please use different behaviors for URLBuilder instead.

5.0.0-beta.4 - 2019-06-16 13:49:01

Added

  • Support for Swift Package Manager in Xcode 11
  • New convenient DSL, that allows to convert this code:
func deleteSession() -> APIRequest<Empty, UnknownError> {
    let request : APIRequest<Empty, UnknownError> = tron.codable.request("session")
    request.method = .delete
    return request
}

into:

func deleteSession() -> APIRequest<Empty, UnknownError> {
    return tron.codable.request("session").delete()
}

Read more about other DSL improvements in 5.0 Migration Guide

Changed

  • URLBuilder now resolves URL's using URL(string:relativeTo:) method, thus allowing more flexible url creation.

5.0.0-beta.3 - 2019-05-17 10:59:30

Added

  • Ability to traverse json to any level when using SwiftyJSONDecodable.

Changed

  • Improved response serializer behavior for cases where requests were stubbed using URLProtocol.
  • TRON 5 requires RxSwift 5 and SwiftyJSON 5
  • Core is now a default subspec for CocoaPods installation method. To use SwiftyJSON, add following to Podfile:
pod 'TRON/SwiftyJSON'

4.2.2 - 2019-04-08 13:36:49

  • Swift 4.2 version is now specified in podspec for users of CocoaPods 1.6.x.

This is the last release that supports Xcode 9 and Swift 3.

5.0.0-beta.2 - 2019-04-06 17:07:54

Changed

  • ErrorSerializable protocol changed to have non-optional initializer and to not accept serializedObject. It no longer can be used for additional object validation, but behavior should be more predictable and straightforward - ErrorSerializable should only be created if somebody (URL loading system, Alamofire or Model serialization) actually reported error.
  • codable is now a lazy stored property instead of computed property.
  • CodableSerializer API has been made open to allow easy customization in subclasses.
  • NetworkLoggerPlugin now has a constructor that allows to easily configure it's behavior.

Breaking

  • Properties that worked with Alamofire.SessionManager have been renamed to session to be in line with Alamofire renaming of SessionManager to Session.

Removed

  • TRON.defaultAlamofireManager() method. TRON initializer now uses Alamofire.Session.default as a replacement.
  • TRON.processingQueue property

5.0.0-beta.1 - 2019-03-12 13:58:35

TRON now requires:

  • Xcode 10
  • Swift 4 and higher
  • iOS/tvOS 10 and higher
  • watchOS 3 and higher
  • macOS 10.12 and higher

Added

  • Complete documentation
  • TRONDataResponseSerializer and TRONDownloadResponseSerializer structs to encapsulate serialization of responses using closure.
  • All test suite now uses StubbingURLProtocol to stop tests from sending network requests. Closes #21.
  • Additional JSONDecodable conformances have been added for arithmetic types such as Int8...Int64, UInt8...64.

Removed

  • EmptyResponse type was replaced with Alamofire.Empty struct to not interfere with Alamofire.EmptyResponse protocol.
  • TRON.stubbingShouldBeSuccessful property since now each stub directly defines it's behavior instead of global setting
  • Conditional conformance of Array to JSONDecodable
  • Convenience download methods on CodableSerializer and JSONDecodableSerializer
  • AuthorizationRequirement enum. Please use Alamofire.RequestAdapter and Session.adapter property to adapt request, when additional headers are needed.
  • HeaderBuildable protocol and HeaderBuilder types. Please use BaseRequest.headers property of type Alamofire.HTTPHeaders directly.
  • ErrorHandlingDataResponseSerializerProtocol and ErrorHandlingDownloadResponseSerializer protocol. Now, ErrorModel on all requests conforms to ErrorSerializable protocol, that contains initializer that allows to create it directly.
  • CodableDownloadParser and JSONDecodableDownloadParser, replacement class DownloadSerializer has been introduced, it allows to create a data model by implementing DownloadResponseSerializerProtocol

Breaking

  • headers property of BaseRequest now contains HTTPHeaders type instead of [String: String]. Please note that along with HeaderBuilder removal TRON no longer sends 'Accept':'application/json' header by default.
  • APIError is changed to be able to accept SerializedObject and is now a class with nullable initializer. Also, DownloadAPIError type has been introduced for download errors, that do not have Data in them, but have fileURL instead.
  • Plugin methods that previously accepted APIError<ErrorModel> now accept ErrorModel directly. Added didSuccessfullyParseDownloadResponse and didReceiveDownloadResponse methods.
  • All concrete implementations of DataResponseSerializerProtocol such as CodableParser and JSONDecodableParser now have only one generic argument - Model and are only parsing model type.
  • JSONDecodableParser and CodableParser now have only one generic argument - Model, since ErrorModel is now moved to ErrorSerializable protocol, that does not depend on any particular serializer.
  • APIStub has been rewritten from scratch to allow injecting only results of network request(URLRequest, HTTPURLResponse, Data, Error and fileURL) as opposed to actual Model and errorModel as well as definition of successful/unsuccessful requests. APIStub now is been attached to Alamofire.Request when stubbing for this particular request has been enabled. Rewrite also allows plugin callbacks to be called more consistently for both stubbed and unstubbed cases.
  • rxMultipartResult method on UploadRequest method was removed since UploadRequest for multipart requests in Alamofire 5 is now synchronous and now does not require special handling. You can now call rxResult replacement method instead.

4.2.0 - 2018-04-12 15:57:28

  • Implemented JSONDecodable support for Arrays, whose Element is JSONDecodable. Requires Swift 4.1 to work.

4.1.2 - 2018-02-27 19:16:55

  • Improved output of NetworkLoggerPlugin, which now includes status codes, HTTP body contents, and some emojis 🚀

4.1.1 - 2018-02-27 13:34:45

  • Allow EmptyResponse to be used with CodableParser when network response does not contain valid JSON.

4.1.0 - 2017-12-28 15:58:37

  • Deprecate TRON level headerBuilder in favor of Alamofire.requestAdapter.
  • Loosen CodableParser generic constraints to be able to work with Decodable protocol instead of Codable.

4.0.0 - 2017-11-15 13:51:19

  • Compiled with Xcode 9.1 / Swift 4.0.2

4.0.0-beta.2 - 2017-09-29 19:10:53

  • RxSwift dependency is bumped up to 4.0.0-beta.0, SwiftyJSON dependency is bumped to 4.0.0-alpha.1
  • Binary release is compiled with Xcode 9.0 Swift 4 compiler.
  • Added ability to customize JSONSerialization.ReadingOptions for JSONDecodableSerializer. Unlike previous releases, no options are specified by default(Previously SwiftyJSON used allowFragments option).

To have old behavior that allows fragmented json, use:

let request = tron.swiftyJSON(readingOptions: .allowFragments).request("path")
  • Added ability to customize JSONDecoder for CodableSerializer for both model parsing and error parsing.
  • CodableParser and JSONDecodableParser are now classes, that are subclassible.

4.0.0-beta.1 - 2017-09-10 18:29:47

This is major release, containing breaking API changes, please read TRON 4.0 Migration Guide

  • Implemented support for Codable protocol.
  • APIError now takes it's localizedDescription from underlying errorModel if that model is LocalizedError, and from error.localizedDescription if not.

Breaking changes

  • SwiftyJSONDecodable methods are now prefixed with .swiftyJSON, like so:
// old
let request = tron.request("path")
// new
let request = tron.swiftyJSON.request("path")

3.1.1 - 2017-09-04 16:05:23

  • Makes NetworkActivityPlugin always use API from the main thread(thanks, @mgurreta)!

3.1.0 - 2017-09-02 18:18:06

  • Preliminary support for Swift 3.2 and Swift 4(Xcode 9 beta 6).

3.0.3 - 2017-05-31 17:20:36

  • Prevent upload requests from being sent using performMultipart method if they are not of .multipartFormData type and vice versa, add specific assertions and error messages.
  • Added isMultipartRequest property on UploadRequestType.

3.0.2 - 2017-05-28 16:43:25

  • Improved SPM integration and CI scripts
  • Added validationClosure properties on APIRequest, DownloadAPIRequest and UploadAPIRequest that allow customizing validation behaviour. Default behaviour, as before, is calling validate() method on Request.

3.0.1 - 2017-02-22 15:40:51

  • Constructors on APIRequest, UploadAPIRequest, APIStub are made public to allow overriding in subclasses.

3.0.0 - 2017-02-18 18:04:48

Breaking changes

  • dispatchQueue property was removed from Plugin protocol. All Plugin events are now called synchronously to allow write access to objects, expecially requests. If Plugin requires any specific dispatch queue to run on, it must implement this logic itself.

Bugfixes

  • APIStub will now print error to console, if it failed to build model from file.
  • NetworkLoggerPlugin will no longer double escape characters, when printing successful request cURL to console.

2.0.1 - 2016-11-30 13:56:58

Lowers deployment targets to iOS 8 / macOS 10.10

Required dependencies: Alamofire 4.1 and higher.

2.0.0 - 2016-10-31 15:09:11

  • NetworkLoggerPlugin now has logCancelledRequests property, that allows turning off logging of failing requests, that were explicitly cancelled by developer.
  • Ensures, that APIStub always calls completion blocks, even if response is not successfully created.

2.0.0-beta.5 - 2016-10-31 15:08:54

  • APIStub creation is deferred until apiStub property is called.
  • APIStub model and error properties have been replaced with modelClosure and errorClosure, that are created in APIStub constructor. This is done to allow APIStub to function even if APIRequest has been deallocated.

2.0.0-beta.4 - 2016-10-31 15:08:30

  • Plugin protocol was rewritten to contain generic methods and more information about what is happening in TRON ecosystem.
  • ErrorHandlingDataResponseSerializerProtocol and ErrorHandlingDownloadResponseSerializerProtocol now have protocol extensions for default SwiftyJSON error parsing.

2.0.0-beta.3 - 2016-09-24 15:05:54

  • Parseable protocol was rewritten to directly inherit from Alamofire.DataResponseSerializerProtocol. It allows us to have two generic typealiases - SerializedObject from Alamofire and added to it SerializedError from TRON.
  • Fixed issue, that could cause completion handlers to be called on background thread.
  • Use SwiftyJSON instead of forked SwiftyJSON3
  • parameters property on BaseRequest now contains [String:Any] instead of [String:AnyObject]
  • processingQueue property on APIRequest was removed.

2.0.0-beta.2 - 2016-09-21 08:58:38

IMPORTANT Please check out meta-ticket, discussing response serialization changes in beta-2. Feedback is really important for next betas and final release.

  • Parseable protocol was rewritten from generic static func to having associatedtype and a func inside. This is done to broaden support for any kinds of mappers. Good example would be CoreData objects creation, that requires different contexts for background threads, and was difficult to implement using old syntax.
  • All TRON request methods now contain obligatory responseParser and errorParser properties, that define, how response and errors are parsed.
  • APIStub objects are now using responseParser and errorParser of the request, and therefore have been entirely rewritten with new properties, that accept Data objects instead of directly setting result.
  • JSONDecodable protocol now provides convenience methods to use with TRON and APIRequest, that work the same as before, therefore almost maintaining backwards code compatibility for TRON/SwiftyJSON subspec.
  • ErrorBuilder class was removed.

2.0.0-beta.1 - 2016-09-17 11:41:31

TRON 2.0 is supported on iOS 9.0, macOS 10.11 and higher due to Alamofire.framework required versions. Read [migration guide](/Docs/2.0 Migration Guide.md) for overview of API changes.

NOTE This release uses forked SwiftyJSON, and SwiftyJSON3 cocoapod, because original repo has not been updated to Swift 3. In future release we hope to use SwiftyJSON cocoapod. Comment on SwiftyJSON repo.

API changes

  • ResponseParseable was rewritten and renamed to Parseable. It now allows creating models without using a constructor. Therefore, it's now possibly to use really any kind of mapper and make factory-like response builders.
  • Success blocks on APIRequest are now optional and equal to nil by default.
  • MultipartAPIRequest now becomes a part of larger UploadAPIRequest class.
  • Introduced new DownloadAPIRequest class, that receives part of APIRequest functionality.

Renamings

  • Swift 3 API design guidelines have been applied to all API.
  • perform(completion:) method was renamed to performCollectingTimeline(withCompletion:) to better match method internal behaviour
  • encoding property on TRON and requests has been renamed to parameterEncoding and now has a different type - Alamofire.ParameterEncoding

Removals

  • responseBuilder property on APIRequest was removed, as it's no longer used when parsing received response.
  • JSONDecodable extension on Array is temporarily unavailable due to issues with Swift compiler.
  • encodingStrategy property on TRON was removed - please use parameterEncoding instead.
  • TRON.RESTencodingStrategy and TRON.URLEncodingStrategy were removed - please refer to Alamofire 4 migration guide for details.
  • RequestType enum was replaced by UploadRequestType and DownloadRequestType on DownloadAPIRequest and UploadAPIRequest classes.