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:
postgetputpatchdeleteheadconnectoptionstracenoneif 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) -> Selffunc set(stringBody: String, encoding: String.Encoding = .utf8) -> Selffunc set(jsonBody: [String: Any]) -> Selffunc set(arrayJsonBody: [Any]) -> Selffunc set<EObject: Encodable>(jsonEncodable: EObject) -> Selffunc 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: Boolwhich is true if request is failedisSucceed: Boolwhich 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: HTTPURLCompatiblewhich is origin url of the responseheaders: Headerwhich is headers of the responsebody: Data?which is body of the responsestatusCode: Intwhich 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.statusThe statuses are:
running(Float)which contains percentage of request progress from 0 - 1droppedidlecompleted(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: Boolwhich is true if request is failedisSucceed: Boolwhich 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:
resumedfailToResume
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 -> Stringfunc parseJSONBody() throws -> [String: Any]func parseArrayJSONBody() throws -> [Any]func parseJSONBody<DObject: Decodable>() throws -> DObjectfunc parseArrayJSONBody<DObject: Decodable>() throws -> [DObject]func parseJSONBody<DOBject: Decodable>(forType type: DOBject.Type) throws -> DOBjectfunc 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) -> Selfvalidate(statusCodes: Range<Int>) -> Selfvalidate(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:
validinvalidinvalidWithReason(String)invalid with custom reason which will be a description onHTTPErrorError
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: Boolwhich will be true if one or more request is failedareCompleted: Boolwhich 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