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.
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 AlamofireDataResponseSerializerProtocol, we are adding one additional protocol for error-handling.
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
})
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
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.
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
Codable
protocol.Overview
We designed TRON to be simple to use and also very easy to customize. After initial setup, using TRON is very straightforward:
Requirements
Installation
Swift Package Manager
TRON framework includes Codable implementation. To use SwiftyJSON,
import TRONSwiftyJSON
framework. To use RxSwift wrapper,import RxTRON
.CocoaPods
Only Core subspec, without SwiftyJSON dependency:
RxSwift extension for TRON:
Migration Guides
Project status
TRON
is under active development by MLSDev Inc. Pull requests are welcome!Request building
TRON
object serves as initial configurator forAPIRequest
, setting all base values and configuring to use with baseURL.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.By default,
TRON
usesURLBuilder
class, that simply appends relative path to base URL, which is sufficient in most cases. You can customize url building process globally by changingurlBuilder
property onTRON
or locally, for a single request by modifyingurlBuilder
property onAPIRequest
.Sending requests
To send
APIRequest
, callperform(withSuccess:failure:)
method onAPIRequest
:Alternatively, you can use
performCollectingTimeline(withCompletion:)
method that containsAlamofire.Response
inside completion closure:In both cases, you can additionally chain
Alamofire.Request
methods, if you need:Response parsing
Generic
APIRequest
implementation allows us to define expected response type before request is even sent. On top ofAlamofire
DataResponseSerializerProtocol
, we are adding one additional protocol for error-handling.Codable
Parsing models using Swift4
Codable
protocol is simple, implementCodable
protocol:And send a request:
It’s possible to customize decoders for both model and error parsing:
JSONDecodable
TRON
providesJSONDecodable
protocol, that allows us to parse models using SwiftyJSON:To parse your response from the server using
SwiftyJSON
, all you need to do is to createJSONDecodable
conforming type, for example:And send a request:
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: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 bySwiftyJSON.JSON
object while parsing data of the response:Swift Concurrency
Sending requests using Swift Concurrency is done via a proxy object
RequestSender
(orDownloadRequestSender
for download requests). Simple usage example:If you prefer to receive result, containing either successful Model, or ErrorModel, you can do that too:
There is also
response
async property, containing all request information, if you need it:Upload request
For upload requests, it’s useful to monitor upload progress, and show it to the user:
Download request
Similarly to upload requests, download requests have downloadProgress property implemented as async sequence:
If you only care about downloaded file URL, and not parsed data model, you can await responseURL property on request sender:
RxSwift
Error handling
TRON
includes built-in parsing for errors.APIError
is an implementation ofErrorSerializable
protocol, that includes several useful properties, that can be fetched from unsuccessful request:CRUD
Using these requests is really simple:
It can be also nice to introduce namespacing to your API:
This way you can call your API methods like so:
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:Stubbing can be enabled globally on
TRON
object or locally for a singleAPIRequest
. Stubbing unsuccessful requests is easy as well:You can also optionally delay stubbing time:
Upload
Download
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 concreteAPIRequest
. Keep in mind, that plugins that are added toTRON
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 oneTRON
instance in your application.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
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!