[![CI Status](https://img.shields.io/travis/Anh Phan Tran/KiNetworking.svg?style=flat)](https://travis-ci.org/Anh Phan Tran/KiNetworking)
This library is a modern network layer built for high configuration + TDD The library has the uasge of Alamofire, Promises and SwiftyJSON to make operations that help you go from API call to model directly.
To run the example project, clone the repo, and run pod install
from the Example directory first.
KiNetworking is available through CocoaPods. To install it, simply add the following lines to your Podfile:
pod 'KiNetworking'
To make a request, you first need to provide the API service that the request is running on. This is so that you can have a service instance for each of the environment that the app runs on.
An API service requires an APIServiceConfiguration to initialize:
let config = APIServiceConfig(
name: "Staging",
base: "https://stagingBaseURL",
commonHeaders: [
"Someheaderkey": "SomeValue"
])
config?.debugEnabled = .request // .none, .request, .response
let service = APIService.init(config!)
After having the service, you can create a request and execute it on the service like so:
let request = Request(method: .get, endpoint: "someEndpoint")
request.execute(on: service).then(...).catch(...)
The service will invoke the request (request protocol) and return with a response (response protocol).
A Request Protocol has these parameters:
public protocol RequestProtocol {
// Endpoint of the request
var endpoint: String { get }
// Request's params that will be encoded into the final request URL
var parameters: Parameters? { get }
// Request specific headers, will override service's header if share the same key, else, append to the service's headers
var additionalHeaders: HeadersDict? { get set }
// Method: .get, .post, .put, .patch, .delete
var method: Alamofire.HTTPMethod { get }
// Request specific cache policy, if nil will use the service's cache policy
var cachePolicy: URLRequest.CachePolicy? { get }
// Request specific timeout, if nil will use the service's timeout interval
var timeout: TimeInterval? { get }
// Queue that we make the request, if nil will use the service's context
var context: DispatchQueue? { get }
// Body of the request: .json, .formURL, .custom, .data
var body: RequestBody? { get set }
func headers(in service: APIServiceProtocol) -> HeadersDict
func fullURL(in service: APIServiceProtocol) throws -> URL
func urlRequest(in service: APIServiceProtocol) throws -> URLRequest
}
A base class called Request which conforms to this protocol is implemented
A Response Protocol has these parameters:
public protocol ResponseProtocol {
/// Type of response (success or failure)
var result: Response.Result { get }
/// Encapsulates the metrics for a session task.
/// It contains the taskInterval and redirectCount, as well as metrics for each request / response
/// transaction made during the execution of the task.
var metrics: ResponseTimeline? { get }
/// Request
var request: RequestProtocol { get }
/// Return the http url response
var httpResponse: HTTPURLResponse? { get }
/// Return HTTP status code of the response
var httpStatusCode: Int? { get }
/// Return the raw Data instance response of the request
var data: Data? { get }
/// Attempt to decode Data received from server and return a JSON object.
/// If it fails it will return an empty JSON object.
/// Value is stored internally so subsequent calls return cached value.
///
/// - Returns: JSON
func toJSON() -> JSON
/// Attempt to decode Data received from server and return a String object.
/// If it fails it return `nil`.
/// Call is not cached but evaluated at each call.
/// If no encoding is specified, `utf8` is used instead.
///
/// - Parameter encoding: encoding of the data
/// - Returns: String or `nil` if failed
func toString(_ encoding: String.Encoding?) -> String?
}
A base class called Response which conforms to this protocol is implemented
The library provide a DataOperation and a JSONOperation out of the box.
DataOperations are just operation which directly returns the data response for you to manipulate.
DecodableOperation or JSONOperation on the other hand can help you return directly models from your request.
// Customer class with Decodable
struct Customer: Decodable {
}
class GetCustomerRecord: DecodableOperation<Customer> {
public init(customerId: Int) {
super.init()
self.request = SampleJWTRequest(method: .get, endpoint: "/users/\(customerId)", parameters: nil, encoder: JSONEncoding.default)
self.request.timeout = 15
}
}
// Customer class with JSON
class Customer {
init?(from json: JSON) {
// Do your init here
}
}
class GetCustomerRecord: JSONOperation<Customer> {
public init(customerId: Int) {
super.init()
self.request = SampleJWTRequest(method: .get, endpoint: "/users/\(customerId)", parameters: nil, encoder: JSONEncoding.default)
self.request.timeout = 15
self.onParseResponse = { json in
return Customer(from: json)
}
}
}
// When you need to invoke the get customer record API:
...
let service = APIService(...)
GetCustomerRecord(customerId: 123).execute(on: service).then { customer in
// Do what you want with customer
}.catch { error in
// Show error
}
Each API service can have an optional delegate if required:
public protocol APIServiceDelegate: class {
func service(_ apiService: APIServiceProtocol, willExecute request: RequestProtocol)
func service(_ apiService: APIServiceProtocol, shouldHandleCode errorCode: Int, on request: RequestProtocol) -> Bool
func service(_ apiService: APIServiceProtocol, handleResponse: ResponseProtocol, on request: RequestProtocol) -> Promise<ResponseProtocol>
}
This delegate can be used to authorize or refresh access tokens of requests.
Anh Phan Tran, [email protected]
KiNetworking is available under the MIT license. See the LICENSE file for more info.