iONess (iOS Network Session) is HTTP Request Helper for the iOS platform used by Home Credit Indonesia iOS App. It uses Ergo as a concurrent helper and promise pipelining.
Example
To run the example project, clone the repo, and run pod install from the Example directory first.
Requirements
Swift 5.0 or higher (or 5.1 when using Swift Package Manager)
iOS 10 or higher (latest version)
iOS 8 or higher (1.2.5 version)
Only Swift Package Manager
macOS 10.10 or higher
tvOS 10 or higher
Installation
Cocoapods
iONess is available through CocoaPods. To install it, simply add the following line to your Podfile:
for iOS 10 or higher
pod 'iONess', '~> 2.0'
or for iOS 8 or higher
pod 'iONess', '~> 1.2.5'
Swift Package Manager from XCode
Add it using xcode menu File > Swift Package > Add Package Dependency
iONess is available under the MIT license. See the LICENSE file for more info.
Usage Example
Basic Usage
iONess is designed to simplify the request process for HTTP Requests. All you need to do is just create the request using Ness / NetworkSessionManager class:
Ness.default
.httpRequest(.get, withUrl: "https://myurl.com")
.dataRequest()
.then { result in
// do something with result this will not executed when request failed
}
When data dataRequest() is called, it will always execute the request right away no matter it has completion or not.
dataRequest() actually returning Promise object from Ergo so you could always do everything you can do with Ergo Promise:
Ness.default
.httpRequest(.get, withUrl: "https://myurl.com")
.dataRequest()
.then { result in
// do something with result this will not executed when request failed
}.handle { error in
// do something if error occurs
}.finally { result, error in
// do something regarding of error or not after request completed
}
You could always check Ergo here about what its promise can do.
Create Request
To create a request you can do something like this:
it’s better to save the instance of Ness and reused it since it will be just creating the request with the same URLSession unless you want to use any other URLSession for another request.
available enumeration for HTTP Method to use are:
post
get
put
patch
delete
head
connect
options
trace
none if you don’t want to include HTTP Method header
custom(String)
to set a custom type of body, you need to pass those custom type encoder that implements HTTPBodyEncoder object to encode the object into the data:
public protocol HTTPBodyEncoder {
var relatedHeaders: [String: String]? { get }
func encoder(_ any: Any) throws -> Data
}
the relatedHeaders is the associated header with this encoding which will be auto-assigned to the request headers. this variable is optional since the default implementation are returning nil
there some different default method to set the body with iONess default body encoder which are:
After creating a data request, you can just execute the request with then method:
Ness.default
.httpRequest(.get, withUrl: "https://myurl.com")
..
..
.dataRequest()
.then { result in
// do something with result
}
The result is the URLResult object which contains:
urlResponse: URLResponse? which is the original response which you can read the documentation at here
error: Error? which is an error if happens. it will be nil on success response
responseData: Data? which is raw data of the response body
isFailed: Bool which is true if request is failed
isSucceed: Bool which is true if the request is succeed
httpMessage: HTTPResultMessage? which is the response message of the request. It Will be nil if the result is not an HTTP result
The HTTPResultMessage is the detailed HTTP response from the URLResult:
url: HTTPURLCompatible which is the origin URL of the response
headers: Header which is headers of the response
body: Data? which is the body of the response
statusCode: Int which is the status code of the response
You can get the promise object or ignore it. It will return DataPromise which contains the status of the request
let requestPromise = Ness.default
.httpRequest(.get, withUrl: "https://myurl.com")
..
..
.dataRequest()
let status = requestPromise.status
The statuses are:
running(Float) which contains the percentage of request progress from 0 - 1
dropped
idle
completed(HTTPURLResponse) which contains the completed response
error(Error) which contains an error if there are occurs
you can cancel the request using drop() function:
requestPromise.drop()
since the promise is based on the Ergo Promise, it contains the result of the request if it already finished and an error if the error occurs:
// will be nil if the request is not finished yet or if the error occurs
let result = requestPromise.result
// will be nil if an error did not occur or the request is not finished yet
let error = requestPromise.error
// will be true if request completed
print(requestPromise.isCompleted)
Upload Request Promise
Upload requests are the same as Data requests in terms of Promise.
Download Request Promise
Download requests have a slight difference from data requests or upload requests. The download request can be paused and resumed, and the result is different
The result is the DownloadResult object which contains:
urlResponse: URLResponse? which is the original response which you can read the documentation at here
error: Error? which is an error if happens. it will be nil on success response
dataLocalURL: URL? which is the location of downloaded data
isFailed: Bool which is true if request is failed
isSucceed: Bool which is true if the request is succeed
You can pause the download and resume:
request.pause()
let resumeStatus = request.resume()
resume will return ResumeStatus which is enumeration:
resumed
failToResume
Decode Response Body For Data Request
to parse the body, you can do:
let decodedBody = try? result.message.parseBody(using: myDecoder)
the parseBody are accept any object that implement ResponseDecoder. The declaration of ResponseDecoder protocol is like this:
Remember you can put as many validators as you want, which will validate the response using all those validators from the first until the end or until one validator returns invalid
If you don’t provide any URLValidator, then it will be considered invalid if there’s an error or no response from the server, otherwise, all the responses will be considered valid
NetworkSessionManagerDelegate
You can manipulate request or action globally in Session level by using NetworkSessionManagerDelegate:
both methods are optional. The methods will run and functional for:
ness(_: , willRequest: ) will run before any request executed. You can manipulate URLRequest object here and return it or do anything before request and return the current URLRequest
ness(_: , didRequest: ) will run after any request is executed, but not after the request is finished.
RetryControl
You can control when to retry if your request is failed using RetryControl protocol:
public protocol RetryControl {
func shouldRetry(
for request: URLRequest,
response: URLResponse?,
error: Error,
didHaveDecision: (RetryControlDecision) -> Void) -> Void
}
The method will run on a request failure. The only thing you need to do is pass the RetryControlDecision into didHaveDecision closure which is an enumeration with members:
noRetry which will automatically fail the request
retryAfter(TimeInterval) which will retry the same request after TimeInterval
retry which will retry the same request immediately
You can assign RetryControl when preparing a request:
It can be applicable for download or upload requests too.
iONess has some default RetryControl which is CounterRetryControl that the basic algorithm is just counting the failure time and stop retry when the counter reaches the maxCount. to use it, just init the CounterRetryControl when preparing with your maxCount or optionally with TimeInterval before retry. For example, if you want to auto-retry a maximum of 3 times with a delay of 1 second for every retry:
It will ask for RequestDuplicatedDecision depending on what type of duplicated request. The RequestDuplicatedDecision are enumeration with members:
dropAndRequestAgain which will drop the previous request and do a new request with the current completion
dropAndRequestAgainWithCompletion((Param?, URLResponse?, Error?) -> Void) which will drop previous request and do new request with custom completion
ignoreCurrentCompletion which will ignore the current completion, so when the request is complete, it will just run the first request completion
useCurrentCompletion which will ignore the previous completion, so when the request is complete, it will just run the lastest request completion
useBothCompletion which will keep the previous completion, so when the request is complete, it will just run all the request completion
useCompletion((Param?, URLResponse?, Error?) -> Void) which will ignore all completion and use the custom one
The duplicatedHandler is stick to the Ness \ NetworkSessionManager, so if you have duplicated request with different Ness \ NetworkSessionManager, it should not be called.
To assign RequestDuplicatedDecision, you can just assign it to duplicatedHandler property, or just add it when init:
// just handler
let ness = Ness(duplicatedHandler: myHandler)
// with session
let ness = Ness(session: mySession, duplicatedHandler: myHandler)
// using property
ness.duplicatedHandler = myHandler
Or you can just use some default handler:
// just handler
let ness = Ness(onDuplicated: .keepAllCompletion)
// with session
let ness = Ness(session: mySession, onDuplicated: .keepFirstCompletion)
// using property
ness.duplicatedHandler = DefaultDuplicatedHandler.keepLatestCompletion
There are 4 DefaultDuplicatedHandler:
dropPreviousRequest which will drop the previous request and do a new request with the current completion
keepAllCompletion will keep the previous completion, so when the request is complete, it will just run all the request completion
keepFirstCompletion which will ignore the current completion, so when the request is complete, it will just run the first request completion
keepLatestCompletion which will ignore the previous completion, so when the request is complete, it will just run the lastest request completion
iONess
iONess (iOS Network Session) is HTTP Request Helper for the iOS platform used by Home Credit Indonesia iOS App. It uses Ergo as a concurrent helper and promise pipelining.
Example
To run the example project, clone the repo, and run
pod install
from the Example directory first.Requirements
Only Swift Package Manager
Installation
Cocoapods
iONess is available through CocoaPods. To install it, simply add the following line to your Podfile:
for iOS 10 or higher
or for iOS 8 or higher
Swift Package Manager from XCode
Swift Package Manager from Package.swift
Add as your target dependency in Package.swift. Use 2.0.2 as its version for iOS 10 or higher or 1.2.5 for iOS 8 or higher
Use it in your target as
iONess
Contributor
License
iONess is available under the MIT license. See the LICENSE file for more info.
Usage Example
Basic Usage
iONess
is designed to simplify the request process for HTTP Requests. All you need to do is just create the request usingNess
/NetworkSessionManager
class:or with no completion at all:
When data
dataRequest()
is called, it will always execute the request right away no matter it has completion or not.dataRequest()
actually returningPromise
object from Ergo so you could always do everything you can do withErgo Promise
:You could always check Ergo here about what its promise can do.
Create Request
To create a request you can do something like this:
or with customize
URLSession
:it’s better to save the instance of Ness and reused it since it will be just creating the request with the same
URLSession
unless you want to use any otherURLSession
for another request.available enumeration for HTTP Method to use are:
post
get
put
patch
delete
head
connect
options
trace
none
if you don’t want to include HTTP Method headercustom(String)
to set a custom type of body, you need to pass those custom type encoder that implements
HTTPBodyEncoder
object to encode the object into the data:The declaration of
HTTPBodyEncoder
is:the
relatedHeaders
is the associated header with this encoding which will be auto-assigned to the request headers. this variable is optional since the default implementation are returning nilthere some different default method to set the body with iONess default body encoder which are:
func set(body: Data) -> Self
func set(stringBody: String, encoding: String.Encoding = .utf8) -> Self
func set(jsonBody: [String: Any]) -> Self
func set(arrayJsonBody: [Any]) -> Self
func set<EObject: Encodable>(jsonEncodable: EObject) -> Self
func set<EObject: Encodable>(arrayJsonEncodable: [EObject]) -> Self
After the request is ready then prepare the request which will return Thenable:
or for download, you need to give the target location
URL
where you want to downloaded data to be saved:or for upload you need to give file location
URL
which you want to upload:Data Request Promise
After creating a data request, you can just execute the request with then method:
The result is the
URLResult
object which contains:urlResponse: URLResponse?
which is the original response which you can read the documentation at hereerror: Error?
which is an error if happens. it will be nil on success responseresponseData: Data?
which is raw data of the response bodyisFailed: Bool
which is true if request is failedisSucceed: Bool
which is true if the request is succeedhttpMessage: HTTPResultMessage?
which is the response message of the request. It Will be nil if the result is not an HTTP resultThe
HTTPResultMessage
is the detailed HTTP response from theURLResult
:url: HTTPURLCompatible
which is the origin URL of the responseheaders: Header
which is headers of the responsebody: Data?
which is the body of the responsestatusCode: Int
which is the status code of the responseYou can get the promise object or ignore it. It will return
DataPromise
which contains the status of the requestThe statuses are:
running(Float)
which contains the percentage of request progress from 0 - 1dropped
idle
completed(HTTPURLResponse)
which contains the completed responseerror(Error)
which contains an error if there are occursyou can cancel the request using
drop()
function:since the promise is based on the Ergo Promise, it contains the result of the request if it already finished and an error if the error occurs:
Upload Request Promise
Upload requests are the same as Data requests in terms of
Promise
.Download Request Promise
Download requests have a slight difference from data requests or upload requests. The download request can be paused and resumed, and the result is different
The result is the
DownloadResult
object which contains:urlResponse: URLResponse?
which is the original response which you can read the documentation at hereerror: Error?
which is an error if happens. it will be nil on success responsedataLocalURL: URL?
which is the location of downloaded dataisFailed: Bool
which is true if request is failedisSucceed: Bool
which is true if the request is succeedYou can pause the download and resume:
resume will return
ResumeStatus
which is enumeration:resumed
failToResume
Decode Response Body For Data Request
to parse the body, you can do:
the parseBody are accept any object that implement
ResponseDecoder
. The declaration of ResponseDecoder protocol is like this:so you can do something like this:
there are default base decoder you can use if you don’t want to parse from
Data
the
HTTPResultMessage
have default function to automatically parse the body which:func parseBody(toStringEndcoded encoding: String.Encoding = .utf8) throws -> String
func parseJSONBody() throws -> [String: Any]
func parseArrayJSONBody() throws -> [Any]
func parseJSONBody<DObject: Decodable>() throws -> DObject
func parseArrayJSONBody<DObject: Decodable>() throws -> [DObject]
func parseJSONBody<DOBject: Decodable>(forType type: DOBject.Type) throws -> DOBject
func parseArrayJSONBody<DObject: Decodable>(forType type: DObject.Type) throws -> [DObject]
Validator
You can add validation for the response like this:
If the response is not valid, then it will have an error or be dispatched into
handle
closure with an error.the provided validate method are:
validate(statusCode: Int) -> Self
validate(statusCodes: Range<Int>) -> Self
validate(shouldHaveHeaders headers: [String:String]) -> Self
validate(_ validation: HeaderValidator.Validation, _ headers: [String: String]) -> Self
You can add custom validator to validate the http response. The type of validator is
URLValidator
:ResponseValidatorResult
is a enumeration which contains:valid
invalid
invalidWithReason(String)
invalid with custom reason which will be a description onNetworkSessionError
Errorand put your custom
ResponseValidator
like this:You can use
HTTPValidator
if you want to validate onlyHTTPURLResponse
and automatically invalidate the other:Remember you can put as many validators as you want, which will validate the response using all those validators from the first until the end or until one validator returns
invalid
If you don’t provide anyURLValidator
, then it will be considered invalid if there’s an error or no response from the server, otherwise, all the responses will be considered validNetworkSessionManagerDelegate
You can manipulate request or action globally in Session level by using
NetworkSessionManagerDelegate
:both methods are optional. The methods will run and functional for:
ness(_: , willRequest: )
will run before any request executed. You can manipulateURLRequest
object here and return it or do anything before request and return the currentURLRequest
ness(_: , didRequest: )
will run after any request is executed, but not after the request is finished.RetryControl
You can control when to retry if your request is failed using
RetryControl
protocol:The method will run on a request failure. The only thing you need to do is pass the
RetryControlDecision
intodidHaveDecision
closure which is an enumeration with members:noRetry
which will automatically fail the requestretryAfter(TimeInterval)
which will retry the same request afterTimeInterval
retry
which will retry the same request immediatelyYou can assign
RetryControl
when preparing a request:It can be applicable for download or upload requests too.
iONess has some default
RetryControl
which isCounterRetryControl
that the basic algorithm is just counting the failure time and stop retry when the counter reaches the maxCount. to use it, just init theCounterRetryControl
when preparing with your maxCount or optionally with TimeInterval before retry. For example, if you want to auto-retry a maximum of 3 times with a delay of 1 second for every retry:DuplicatedHandler
You can handle what to do if there are multiple duplicated request happen with
DuplicatedHandler
:It will ask for
RequestDuplicatedDecision
depending on what type of duplicated request. TheRequestDuplicatedDecision
are enumeration with members:dropAndRequestAgain
which will drop the previous request and do a new request with the current completiondropAndRequestAgainWithCompletion((Param?, URLResponse?, Error?) -> Void)
which will drop previous request and do new request with custom completionignoreCurrentCompletion
which will ignore the current completion, so when the request is complete, it will just run the first request completionuseCurrentCompletion
which will ignore the previous completion, so when the request is complete, it will just run the lastest request completionuseBothCompletion
which will keep the previous completion, so when the request is complete, it will just run all the request completionuseCompletion((Param?, URLResponse?, Error?) -> Void)
which will ignore all completion and use the custom oneThe duplicatedHandler is stick to the
Ness
\NetworkSessionManager
, so if you have duplicated request with differentNess
\NetworkSessionManager
, it should not be called.To assign
RequestDuplicatedDecision
, you can just assign it toduplicatedHandler
property, or just add it when init:Or you can just use some default handler:
There are 4
DefaultDuplicatedHandler
:dropPreviousRequest
which will drop the previous request and do a new request with the current completionkeepAllCompletion
will keep the previous completion, so when the request is complete, it will just run all the request completionkeepFirstCompletion
which will ignore the current completion, so when the request is complete, it will just run the first request completionkeepLatestCompletion
which will ignore the previous completion, so when the request is complete, it will just run the lastest request completionContribute
You know how, just clone and do pull request