NamadaEatr
NamadaEatr is HTTP Request Helper for iOS platform. it is the updated, better and more general version of my previous library iOSEatr
Example
To run the example project, clone the repo, and run pod install
from the Example directory first.
Requirements
Installation
NamadaEatr is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'NamadaEatr'
Author
nayanda, [email protected]
License
NamadaEatr is available under the MIT license. See the LICENSE file for more info.
Usage Example
Basic Usage
NamadaEatr is designed to simplify the request process for HTTP Request. All you need to do is just create the request using Eatr / HTTPRequestManager class:
Eatr.default
.httpRequest(.get, withUrl: "https://myurl.com")
.prepareDataRequest()
.then { result in
// do something with result
}
Create Request
To create request you can do something like this:
Eatr.default.httpRequest(.get, withUrl: "https://myurl.com")
.set(urlParameters: ["param1": "value1", "param2": "value2"])
.set(headers: ["Authorization": myToken])
.set(body: dataBody)
..
..
or with customize URLSession
:
// create session
var session = URLSession()
session.configuration = myCustomConfig
session.delegateQueue = myOperationQueue
..
..
// create Eatr instance
let eatr = Eatr(with: session)
// create request
eatr.httpRequest(.get, withUrl: "https://myurl.com")
.set(urlParameters: ["param1": "value1", "param2": "value2"])
.set(headers: ["Authorization": myToken])
.set(body: dataBody)
..
..
it's better to save the instance of Eatr and reused it since it will be just creating the request with same URLSession
unless you want to use any other URLSession
for other 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 custom type of body, you need to pass those custom type encoder which implement HTTPBodyEncoder
object to encode the object into the data:
Eatr.default.httpRequest(.get, withUrl: "https://myurl.com")
.set(body: myObject, with encoder: myEndoder) -> Self
..
..
The declaration of HTTPBodyEncoder
is:
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 auto assigned to the request headers. those method are optional since the default implementation are returning nil
there some different default method to set the body with NamadaEatr 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:
Eatr.default.httpRequest(.get, withUrl: "https://myurl.com")
.set(urlParameters: ["param1": "value1", "param2": "value2"])
.set(headers: ["Authorization": myToken])
.set(body: dataBody)
..
..
.prepareDataRequest()
or for download, you need to give target location URL
where you want to downloaded data to be saved:
Eatr.default.httpRequest(.get, withUrl: "https://myurl.com")
.set(urlParameters: ["param1": "value1", "param2": "value2"])
.set(headers: ["Authorization": myToken])
.set(body: dataBody)
..
..
.prepareDownloadRequest(targetSavedUrl: myTargetUrl)
or for updload you need to give file location URL
which you want to upload:
Eatr.default.httpRequest(.get, withUrl: "https://myurl.com")
.set(urlParameters: ["param1": "value1", "param2": "value2"])
.set(headers: ["Authorization": myToken])
.set(body: dataBody)
..
..
.prepareUploadRequest(withFileLocation: myTargetUrl)
Data Request Thenable
After creating data request, you can just execute the request with then method:
Eatr.default
.httpRequest(.get, withUrl: "https://myurl.com")
..
..
.prepareDataRequest()
.then { result in
// do something with result
}
Or with separate completion:
Eatr.default
.httpRequest(.get, withUrl: "https://myurl.com")
..
..
.prepareDataRequest()
.then(run: { result in
// do something when get response
}, whenFailed: { result in
// do something when failed
}
)
With custom dispatcher which will be the thread where completion run:
Eatr.default
.httpRequest(.get, withUrl: "https://myurl.com")
..
..
.prepareDataRequest()
.completionDispatch(on: .global(qos: .background))
.then(run: { result in
// do something when get response
}, whenFailed: { result in
// do something when failed
}
)
The default dispatcher is DispatchQueue.main
Or even without completion:
Eatr.default
.httpRequest(.get, withUrl: "https://myurl.com")
..
..
.prepareDataRequest()
.executeAndForget()
The result is URLResult
object which contains:
urlResponse: URLResponse?
which is original response which you can read the documentation at hereerror: Error?
which is error if happen. 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 request is successhttpMessage: HTTPResultMessage?
which is response message of the request. Will be nil if the result is not http result
The HTTPResultMessage
is the detailed http response from the URLResult
:
url: HTTPURLCompatible
which is origin url of the responseheaders: Header
which is headers of the responsebody: Data?
which is body of the responsestatusCode: Int
which is statusCode of the response
You can get the thenable object or ignore it. It will return HTTPRequest
which contains status of the request
let request = Eatr.default
.httpRequest(.get, withUrl: "https://myurl.com")
..
..
.prepareDataRequest()
.executeAndForget()
let status = request.status
The statuses are:
running(Float)
which contains percentage of request progress from 0 - 1dropped
idle
completed(HTTPURLResponse)
which contains the completed responseerror(Error)
which contains error if there are occurs
you can cancel the request using drop()
function:
request.drop()
Upload Request Thenable
Upload request basically are the same with Data request in terms of thenable.
Download Request Thenable
Download request have slightly difference than data request or upload request. The download request can be paused and resumed, and the result is different
The result is DownloadResult
object which contains:
urlResponse: URLResponse?
which is original response which you can read the documentation at hereerror: Error?
which is error if happen. 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 request is success
You can pause the download and resume:
request.pause()
let status = 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:
public protocol ResponseDecoder {
associatedtype Decoded
func decode(from data: Data) throws -> Decoded
}
so you can do something like this:
class MyResponseDecoder: ResponseDecoder {
typealias Decoded = MyObject
func decode(from data: Data) throws -> MyObject {
// do something to decode data into MyObject
}
}
there are default base decoder you can use if you don't want to parse from Data
class MyJSONResponseDecoder: BaseJSONDecoder<MyObject> {
typealias Decoded = MyObject
override func decode(from json: [String: Any]) throws -> MyObject {
// do something to decode json into MyObject
}
}
class MyStringResponseDecoder: BaseStringDecoder<MyObject> {
typealias Decoded = MyObject
override func decode(from string: String) throws -> MyObject {
// do something to decode string into MyObject
}
}
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:
Eatr.default
.httpRequest(.get, withUrl: "https://myurl.com")
..
..
.prepareDataRequest()
.validate(statusCodes: 0..<300)
.validate(shouldHaveHeaders: ["Content-Type": "application/json"])
.then(run: { result in
// do something when get response
}, whenFailed: { result in
// do something when failed
}
)
If the response is not valid, then it will have error or dispacthed into whenFailed closure with error.
the provided validate method are:
validate(statusCode: Int) -> Self
validate(statusCodes: Range<Int>) -> Self
validate(shouldHaveHeaders headers: [String:String]) -> Self
You can add custom validator to validate the http response. The type of validator is URLValidator
:
public protocol HTTPValidator {
func validate(for response: URLResponse) -> ValidationResult
}
ValidationResult
is a enumeration which contains:
valid
invalid
invalidWithReason(String)
invalid with custom reason which will be a description onHTTPError
Error
and put your custom URLValidator
like this:
Eatr.default
.httpRequest(.get, withUrl: "https://myurl.com")
..
..
.prepareDataRequest()
.validate(using: MyCustomValidator())
.then(run: { result in
// do something when get response
}, whenFailed: { result in
// do something when failed
}
)
You can use HTTPValidator
if you want to validate only HTTPURLResponse
and automatically invalidate the other:
public protocol HTTPValidator: URLValidator {
func validate(forHttp response: HTTPURLResponse) -> URLValidatorResult
}
Remember you can put as many validator as you want, which will validate the response using all those validator from the first until end or when one validator return invalid
If you don't provide any URLValidator
, then it will considered invalid if there's error or no response from the server, otherwise, all the response will be considered valid
Aggregate
You can aggregate two or more request into one single Thenable like this:
request1.aggregate(with: request2)
.aggregate(request3)
.then { allResults in
// do something with allResults
}
//or like this
RequestAggregator(aggregatedRequests)
.then { allResults in
// do something with allResults
}
the result are RequestAggregator.Result
which contains:
results: [AggregatedResult]
which is all the completed results from aggregated requestsisFailed: Bool
which will be true if one or more request is failedareCompleted: Bool
which will be true if all the request is completed
You can get the request too like single Request and get its status or drop it just like one request:
let aggregatedRequests = request1.aggregate(with: request2)
.aggregate(request3)
.then { allResults in
// do something with allResults
}
let aggregatedStatus = aggregatedRequests.status
aggregatedRequests.drop()
Contribute
You know how, just clone and do pull request