Related
the following is my APIManager code, I'm using it in all my apps. But sometimes, the guard statement fails in connectToServer function, which means the statusCode of HTTPURLResponse other than 200...299 and the thing here is even after getting statusCode other than 200...299 my record got inserted into DB. I don't know what happens.
I thought that the cause of this behavior is from ServerURL, because I'm using a dev server with IP address http://00.000.0.000/ without security. Once I moved it to domain as https://XXX.XXXXXXXXXX.XXXXX/ it is working fine. Can you help me to figure out this?
And also will it supports for asynchronous calls?
import UIKit
struct APIResponse : Decodable {
let status : Bool
let message : String
let extra: String?
}
internal let BASE_URL = "http://00.000.0.00/app/v0_1/api/" // Example server URL
enum APIPath: String {
case registration = "registration"
case login = "login"
case getProfile = "get_profile"
func directURL() -> URL? {
let urlPath = BASE_URL + self.rawValue
return URL(string: urlPath)
}
func extendedURL(using parameters: [String: Any]) -> URL? {
let extendedPath = parameters.map { $0.key + "=" + "\($0.value)" }.joined(separator: "&")
let urlPath = BASE_URL + self.rawValue + "?" + extendedPath
return URL(string: urlPath)
}
}
enum APIMethod: String {
case get = "GET"
case put = "PUT"
case post = "POST"
case patch = "PATCH"
case delete = "DELETE"
}
enum APIHeaders {
case user
case app
var authorization: [String:String] {
let acceptLanguage = UserDefaults.standard.value(forKey: UDKeys.appleLanguage) as? String ?? ""
if self == .user {
let token = UserDefaults.standard.value(forKey: UDKeys.userToken) as? String ?? ""
return ["Content-Type": "application/json", "Accept": "application/json", "Accept-Language": acceptLanguage, "Token" : token]
}
return ["Content-Type": "application/json", "Accept": "application/json", "Accept-Language": acceptLanguage]
}
}
struct APIRequest {
var url: URL?
var method: String
var parameters: Data?
var headers: [String:String]
init(path: APIPath, method: APIMethod, headers: APIHeaders) {
self.url = path.directURL()
self.method = method.rawValue
self.headers = headers.authorization
}
init(path: APIPath, parameters: [String: Any], method: APIMethod, headers: APIHeaders) {
self.url = path.extendedURL(using: parameters)
self.method = method.rawValue
self.headers = headers.authorization
}
init(path: APIPath, method: APIMethod, body: [String:Any], headers: APIHeaders) {
self.url = path.directURL()
self.method = method.rawValue
self.parameters = try? JSONSerialization.data(withJSONObject: body, options: .sortedKeys)
self.headers = headers.authorization
}
init<Encode: Encodable>(path: APIPath, method: APIMethod, body: Encode, headers: APIHeaders) {
self.url = path.directURL()
self.method = method.rawValue
self.parameters = try? JSONEncoder().encode(body)
self.headers = headers.authorization
}
}
struct APIError: Error {
let reason: String
let code: String?
init(reason: String, code: String? = nil) {
self.reason = reason
self.code = code
}
}
struct APIDispatcher {
static let instance = APIDispatcher()
private init() {}
func dispatch<Decode: Decodable>(request: APIRequest, response: Decode.Type, result: #escaping (Result<Decode, APIError>) -> ()) {
DispatchQueue(label: "queue", attributes: .concurrent).async {
self.connectToServer(with: request) { (resultant) in
switch resultant {
case .success(let data):
do {
let decoded = try JSONDecoder().decode(response, from: data)
DispatchQueue.main.async {
result(.success(decoded))
}
} catch let decodedError {
print("[Decoded Error]: ", decodedError)
do {
let apiResponse = try JSONDecoder().decode(APIResponse.self, from: data)
let apiError = APIError(reason: apiResponse.message, code: apiResponse.extra)
DispatchQueue.main.async {
result(.failure(apiError))
}
} catch {
let apiError = APIError(reason: decodedError.localizedDescription)
DispatchQueue.main.async {
result(.failure(apiError))
}
}
}
case .failure(let error):
DispatchQueue.main.async {
result(.failure(error))
}
}
}
}
}
func dispatch(request: APIRequest, result: #escaping (Result<Dictionary<String,Any>, APIError>) -> ()) {
DispatchQueue(label: "queue", attributes: .concurrent).async {
self.connectToServer(with: request) { (resultant) in
switch resultant {
case .success(let data):
do {
let serialized = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! Dictionary<String,Any>
DispatchQueue.main.async {
result(.success(serialized))
}
} catch {
let error = APIError(reason: error.localizedDescription)
DispatchQueue.main.async {
result(.failure(error))
}
}
case .failure(let error):
DispatchQueue.main.async {
result(.failure(error))
}
}
}
}
}
private func connectToServer(with request: APIRequest, result: #escaping (Result<Data, APIError>) -> ()) {
guard let url = request.url else {
let error = APIError(reason: "Invalid URL")
result(.failure(error))
return
}
var urlRequest = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30)
urlRequest.httpMethod = request.method
urlRequest.httpBody = request.parameters
urlRequest.allHTTPHeaderFields = request.headers
print(urlRequest)
let urlSessionConfiguration = URLSessionConfiguration.default
urlSessionConfiguration.waitsForConnectivity = false
urlSessionConfiguration.timeoutIntervalForRequest = 30
urlSessionConfiguration.timeoutIntervalForResource = 60
let urlSession = URLSession(configuration: urlSessionConfiguration)
urlSession.dataTask(with: urlRequest) { (data, response, error) in
if let error = error {
let error = APIError(reason: error.localizedDescription)
result(.failure(error))
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
let error = APIError(reason: "Server Error")
result(.failure(error))
return
}
if let data = data {
result(.success(data))
}
}.resume()
}
}
Note: BASE_URL and APIResponse might be vary according to project.
I'm using it as
func login() {
self.startLoading()
let body = ["mobile_number": phoneNumberTF.text!, "password" : passwordTF.text!, "uuid" : UIDevice.current.identifierForVendor!.uuidString]
let apiRequest = APIRequest(path: .login, method: .post, body: body, headers: .app)
APIDispatcher.instance.dispatch(request: apiRequest) { result in
self.stopLoading()
switch result {
case .success(let response):
break
case .failure(let error):
break
}
}
}
EDIT: My bad I asked completely reverse on statsCode now I modified it.
Recently I have started learning iOS app development using swift so I am new to it. I want to implement rest api call in swift & found that we can achieve this using URLRequest. So I have written generic method to call all type(like get, put, post) of rest api as below.
import Foundation
//import Alamofire
public typealias JSON = [String: Any]
public typealias HTTPHeaders = [String: String];
public enum RequestMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case delete = "DELETE"
}
public enum Result<Value> {
case success(Value)
case failure(Error)
}
public class apiClient{
private var base_url:String = "https://api.testserver.com/"
private func apiRequest(endPoint: String,
method: RequestMethod,
body: JSON? = nil,
token: String? = nil,
completionHandler: #escaping (Data?, URLResponse?, Error?) -> Void) {
let url = URL(string: (base_url.self + endPoint))!
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = method.rawValue
urlRequest.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
if let token = token {
urlRequest.setValue("bearer " + token, forHTTPHeaderField: "Authorization")
}
if let body = body {
urlRequest.httpBody = try? JSONSerialization.data(withJSONObject: body)
}
let session = URLSession(configuration: .default)
let task = session.dataTask(with: urlRequest) { data, response, error in
//NSLog(error)
completionHandler(data, response, error)
}
task.resume()
}
public func sendRequest<T: Decodable>(for: T.Type = T.self,
endPoint: String,
method: RequestMethod,
body: JSON? = nil,
token: String? = nil,
completion: #escaping (Result<T>) -> Void) {
return apiRequest(endPoint: endPoint, method: method, body:body, token: token) { data, response, error in
guard let data = data else {
return completion(.failure(error ?? NSError(domain: "SomeDomain", code: -1, userInfo: nil)))
}
do {
let decoder = JSONDecoder()
try completion(.success(decoder.decode(T.self, from: data)))
} catch let decodingError {
completion(.failure(decodingError))
}
}
}
}
this is how I call it method from controller
public func getProfile(userId :Int, objToken:String) -> Void {
let objApi = apiClient()
objApi.sendRequest(for: ProfileDetails.self,
endPoint:"api/user/profile/\(userId)",
method: .get,
token: objToken,
completion:
{(userResult: Result<ProfileDetails>) -> Void in
switch userResult
{
case .success(let value):
if value.respCode == "01" {
print(value.profile)
do {
//... ddo some taks like store response in local db or else
} catch let error as NSError {
// handle error
print(error)
}
}
else {
//do some task
}
break
case .failure(let error):
print(error)
break
}
})
}
I am decoding server response in below model
class ProfileDetails : Response, Decodable {
var appUpdate : AppUpdate?
var profile : Profile?
enum CodingKeys: String, CodingKey {
case profile = "profile"
case respCode = "resp_code"
case respMsg = "resp_msg"
}
public required convenience init(from decoder: Decoder) throws {
self.init()
let values = try decoder.container(keyedBy: CodingKeys.self)
self.profile = try values.decodeIfPresent(Profile.self, forKey: .profile)
self.respCode = try values.decodeIfPresent(String.self, forKey: .respCode)!
self.respMsg = try values.decodeIfPresent(String.self, forKey: .respMsg)
}
}
This code is not able to handle error response like 401, 404 etc from server. So what I am looking for, is to convert this api (URLRequest)request to generic Alamofire request with error handling like 401, 404 etc. I have install Alamofire pods. Is there anyone who has developed generic Alamofire request method with decoding & error handling?
Thanks in advance :)
Git link: https://github.com/sahilmanchanda2/wrapper-class-for-alamofire
Here is my version(Using Alamofire 5.0.2):
import Foundation
import Alamofire
class NetworkCall : NSObject{
enum services :String{
case posts = "posts"
}
var parameters = Parameters()
var headers = HTTPHeaders()
var method: HTTPMethod!
var url :String! = "https://jsonplaceholder.typicode.com/"
var encoding: ParameterEncoding! = JSONEncoding.default
init(data: [String:Any],headers: [String:String] = [:],url :String?,service :services? = nil, method: HTTPMethod = .post, isJSONRequest: Bool = true){
super.init()
data.forEach{parameters.updateValue($0.value, forKey: $0.key)}
headers.forEach({self.headers.add(name: $0.key, value: $0.value)})
if url == nil, service != nil{
self.url += service!.rawValue
}else{
self.url = url
}
if !isJSONRequest{
encoding = URLEncoding.default
}
self.method = method
print("Service: \(service?.rawValue ?? self.url ?? "") \n data: \(parameters)")
}
func executeQuery<T>(completion: #escaping (Result<T, Error>) -> Void) where T: Codable {
AF.request(url,method: method,parameters: parameters,encoding: encoding, headers: headers).responseData(completionHandler: {response in
switch response.result{
case .success(let res):
if let code = response.response?.statusCode{
switch code {
case 200...299:
do {
completion(.success(try JSONDecoder().decode(T.self, from: res)))
} catch let error {
print(String(data: res, encoding: .utf8) ?? "nothing received")
completion(.failure(error))
}
default:
let error = NSError(domain: response.debugDescription, code: code, userInfo: response.response?.allHeaderFields as? [String: Any])
completion(.failure(error))
}
}
case .failure(let error):
completion(.failure(error))
}
})
}
}
The above class uses latest Alamofire version (as of now Feb 2020), This class covers almost every HTTP Method with option to send data in Application/JSON format or normal. With this class you get a lot of flexibility and it automatically converts response to your Swift Object.
Look at the init method of this class it has:
data: [String,Any] = In this you will put your form data.
headers: [String:String] = In this you can send custom headers that you want to send along with the request
url = Here you can specify full url, you can leave it blank if you already have defined baseurl in Class. it comes handy when you want to consume a REST service provided by a third party. Note: if you are filling the url then you should the next parameter service should be nil
service: services = It's an enum defined in the NetworkClass itself. these serves as endPoints. Look in the init method, if the url is nil but the service is not nil then it will append at the end of base url to make a full URL, example will be provided.
method: HTTPMethod = here you can specify which HTTP Method the request should use.
isJSONRequest = set to true by default. if you want to send normal request set it to false.
In the init method you can also specify common data or headers that you want to send with every request e.g. your application version number, iOS Version etc
Now Look at the execute method: it's a generic function which will return swift object of your choice if the response is success. It will print the response in string in case it fails to convert response to your swift object. if the response code doesn't fall under range 200-299 then it will be a failure and give you full debug description for detailed information.
Usage:
say we have following struct:
struct Post: Codable{
let userId: Int
let id: Int
let title: String
let body: String
}
Note the base url defined in NetworkClass https://jsonplaceholder.typicode.com/
Example 1: Sending HTTP Post with content type Application/JSON
let body: [String : Any] = ["title": "foo",
"body": "bar",
"userId": 1]
NetworkCall(data: body, url: nil, service: .posts, method: .post).executeQuery(){
(result: Result<Post,Error>) in
switch result{
case .success(let post):
print(post)
case .failure(let error):
print(error)
}
}
output:
Service: posts
data: ["userId": 1, "body": "bar", "title": "foo"]
Post(userId: 1, id: 101, title: "foo", body: "bar")
HTTP 400 Request
NetworkCall(data: ["email":"peter#klaven"], url: "https://reqres.in/api/login", method: .post, isJSONRequest: false).executeQuery(){
(result: Result) in
switch result{
case .success(let post):
print(post)
case .failure(let error):
print(error)
}
}
output:
Service: https://reqres.in/api/login
data: ["email": "peter#klaven"]
Error Domain=[Request]: POST https://reqres.in/api/login
[Request Body]:
email=peter%40klaven
[Response]:
[Status Code]: 400
[Headers]:
Access-Control-Allow-Origin: *
Content-Length: 28
Content-Type: application/json; charset=utf-8
Date: Fri, 28 Feb 2020 05:41:26 GMT
Etag: W/"1c-NmpazMScs9tOqR7eDEesn+pqC9Q"
Server: cloudflare
Via: 1.1 vegur
cf-cache-status: DYNAMIC
cf-ray: 56c011c8ded2bb9a-LHR
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
x-powered-by: Express
[Response Body]:
{"error":"Missing password"}
[Data]: 28 bytes
[Network Duration]: 2.2678009271621704s
[Serialization Duration]: 9.298324584960938e-05s
[Result]: success(28 bytes) Code=400 "(null)" UserInfo={cf-ray=56c011c8ded2bb9a-LHR, Access-Control-Allow-Origin=*, Date=Fri, 28 Feb 2020 05:41:26 GMT, expect-ct=max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct", Server=cloudflare, Etag=W/"1c-NmpazMScs9tOqR7eDEesn+pqC9Q", x-powered-by=Express, Content-Type=application/json; charset=utf-8, Content-Length=28, Via=1.1 vegur, cf-cache-status=DYNAMIC}
with custom headers
NetworkCall(data: ["username":"sahil.manchanda2#gmail.com"], headers: ["custom-header-key" : "custom-header-value"], url: "https://httpbin.org/post", method: .post).executeQuery(){(result: Result) in
switch result{
case .success(let data):
print(data)
case .failure(let error):
print(error)
}
}
output:
Service: https://httpbin.org/post
data: ["username": "sahil.manchanda2#gmail.com"]
{
"args": {},
"data": "{\"username\":\"sahil.manchanda2#gmail.com\"}",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "br;q=1.0, gzip;q=0.9, deflate;q=0.8",
"Accept-Language": "en;q=1.0",
"Content-Length": "41",
"Content-Type": "application/json",
"Custom-Header-Key": "custom-header-value",
"Host": "httpbin.org",
"User-Agent": "NetworkCall/1.0 (sahil.NetworkCall; build:1; iOS 13.2.2) Alamofire/5.0.2",
"X-Amzn-Trace-Id": "Root=1-5e58a94f-fab2f24472d063f4991e2cb8"
},
"json": {
"username": "sahil.manchanda2#gmail.com"
},
"origin": "182.77.56.154",
"url": "https://httpbin.org/post"
}
typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode String but found a dictionary instead.", underlyingError: nil))
In the last example you can see typeMismatch at the end, I tried to pass [String:Any] in the executeQuery but since the Any doesn't confirm to encodable I had to use String.
I use EVReflection with alamofire and i think this is one of the best combination to work with.
Use URLRequestConvertible protocol of Alamofire.
This is what i follow.
Just for reference purpose.
Make enum for your all endpoint and confirm that enum to URLRequestConvertible.
enum Router: URLRequestConvertible {
//your all endpoint
static var authToken = ""
case login([String:Any])
var route: Route {
switch self {
case .Login(let dict):
return Route(endPoint: "api/addimagedata", httpMethod: .post)
}
}
func asURLRequest() throws -> URLRequest {
var requestUrl = EnvironmentVariables.baseURL
if let queryparams = route.queryParameters {
requestUrl.appendQueryParameters(queryparams)
}
var mutableURLRequest = URLRequest(url: requestUrl.appendingPathComponent(route.endPath))
mutableURLRequest.httpMethod = route.method.rawValue
//FIXME:- Change the Userdefault Key
if Router.authToken.isEmpty, let token = UserDefaults.standard.string(forKey: "Key"), !token.isEmpty {
Router.authToken = token
}
//FIXME:- Set Mutable Request Accordingly
mutableURLRequest.setValue("Bearer \(Router.authToken)", forHTTPHeaderField: "Authorization")
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Accept")
if route.method == .get {
return try Alamofire.URLEncoding.default.encode(mutableURLRequest, with: route.parameters)
}
return try Alamofire.JSONEncoding.default.encode(mutableURLRequest, with: route.parameters)
}
}
Make One Structure as per your requirement.
struct Route {
let endPath: String
let method: Alamofire.HTTPMethod
var parameters: Parameters?
var queryParameters : [String:String]?
var encoding: Alamofire.ParameterEncoding {
switch method {
case .post, .put, .patch, .delete:
return JSONEncoding()
default:
return URLEncoding()
}
}
}
Now make one generic function that accept URLRequestConvertible and return your model in closure. Something like this.
func GenericApiCallForObject<T : URLRequestConvertible, M : EVObject>(router : T, showHud : Bool = true ,responseModel : #escaping (M) -> ()) {
view.endEditing(true)
if !isConnectedToInternet {
showNetworkError()
return
}
if showhud ? showHud() : ()
Alamofire.request(router).responseObject { (response: DataResponse<M>) in
self.HandleResponseWithErrorForObject(response: response) { (isSuccess) in
if isSuccess {
if let value = response.result.value {
responseModel(value)
}
}
})
}
}
Now make one generic function that accept your response and handle the error for you. Something like this.
func HandleResponseWithErrorForObject<M : EVObject>(response : DataResponse<M>, isSuccess : #escaping (Bool) -> ()) {
print(response)
hideHud()
switch response.response?.statusCode ?? 0 {
case 200...299:
isSuccess(true)
case 401:
isSuccess(false)
showSessionTimeOutError()
case -1005,-1001,-1003:
break
default:
isSuccess(false)
// Parse your response and show error in some way.
}
}
Now Finally, how to use it right??! Indeed now its very simple just two lines of code and you are good to go.
GenericApiCallForObject(router: Router.Login(["xyz":"xyz"])) { (response : GeneralModel) in
print(response)
}
Please note that this will only work if you are getting object in response. If there is an array or string you have to make separate function for that and procedure for that is same as above. You will only get response if there is a success otherwise HandleResponseWithErrorForObject function will automatically handle it for you. Also, some variables might be missing in above explanation.
I'm sharing a specific part for error handling on my REST api.
It will decode inside the following block and probably you can use it for reference.
As you can see that's very simple getting a code and translate into an enumeration.
Alamofire allow that but it depends on your version of library.
Sometimes depends your REST api how handle errors internally, they can not throw a code for example if its Java backend, they can encapsulate the exceptions.
public enum RESTError: Error {
case BadRequest(String, [String]?)
case InternalError(String)
case UnAuthorized(String, [String]?)
case NotFound(String)
case Success
/// <#Description#>
///
/// - Parameters:
/// - code: <#code description#>
/// - message: <#message description#>
/// - globalErrors: <#globalErrors description#>
/// - Returns: <#return value description#>
public static func fromCode(code: Int, message: String, globalErrors: [String]? = nil) -> RESTError {
switch code {
case 400: return RESTError.BadRequest(message, globalErrors)
case 401: return RESTError.UnAuthorized(message, globalErrors)
case 500: return RESTError.InternalError(message)
case 404: return RESTError.NotFound(message)
default: break
}
return RESTError.Success
}
}
Alamofire.request(urlRequest)
.validate(statusCode: 200...500)
.responseJSON(completionHandler: { (response: (DataResponse<Any>)) in
if let statusCode = response.response?.statusCode {
if statusCode != 200 {
// call handler errors function with specific message
if let arrayDictionary = response.result.value as? Dictionary<String,AnyObject> {
var error: RESTError?
if let code = arrayDictionary["status"] as? Int {
let message = arrayDictionary["message"] as! String
let globalErrors = arrayDictionary["globalErrors"] as? [String]
error = RESTError.fromCode(code: code, message: message, globalErrors: globalErrors)
} else {
// Build from error message without code.
let message = arrayDictionary["error_description"] as! String
let codeMsg = arrayDictionary["error"] as! String
let globalErrors = arrayDictionary["globalErrors"] as? [String]
if codeMsg == "invalid_token" && message.starts(with: "Access token expired") {
return
} else {
error = RESTError.fromCode(code: codeMsg, message: message, globalErrors: globalErrors)
}
}
if let _ = error {
errorHandler(error!)
} else {
errorHandler(RESTError.InternalError("Internal API rest error."))
}
} else {
errorHandler(RESTError.fromCode(code: statusCode, message: ""))
}
} else {
if let arrayDictionary = response.result.value as? Dictionary<String,AnyObject> {
handler(arrayDictionary)
}
}
} else {
if let error = response.error {
errorHandler(RESTError.InternalError(error.localizedDescription))
}
}
})
You probably need this function that uses the alamofilre Session Manager to perform requests. You can also set the cookies ant headers etc.. to this session manager so that you will have them to the rest of your requests.
import Alamofire
class NetworkManager : NSObject {
internal typealias SuccessCompletion = (Int?, Any?) -> Void?
internal typealias FailCompletion = (Int?, Error, Any?) -> Void?
var sessionManager : SessionManager!
var request : Request?
var headers : HTTPHeaders! = [:]
override init() {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
sessionManager = SessionManager(configuration: configuration)
}
func sendRequest(url: String?, method: String, parameters: [String: Any], success: SuccessCompletion?, fail: FailCompletion?){
var encoding : ParameterEncoding!
if HTTPMethod(rawValue: method) == HTTPMethod.post {
encoding = JSONEncoding.default
} else {
encoding = URLEncoding.default
}
request = sessionManager.request(url ?? "", method: HTTPMethod(rawValue: method)!, parameters: parameters, encoding: encoding, headers: headers)
.validate()
.responseData{response in
switch (response.result) {
case .success:
let statusCode = response.response?.statusCode
success?(statusCode, response.result.value)
self.request = nil
break
case .failure(let error):
let statusCode = response.response?.statusCode
fail?(statusCode, error, response.data)
self.request = nil
break
}
}
}
}
EDIT
To add Headers you can just add a function like this..
func updateJSONHeader(token: String) {
self.clearHeaders()
headers["AuthorizationToken"] = "\(token)"
}
For cookie
func setCookie(_ cookie : HTTPCookie?){
if let cookie = cookie {
HTTPCookieStorage.shared.setCookie(cookie)
}
}
Clear headers
func clearHeaders(){
headers = [:]
}
And keep in mind that it's a singleton class so whenever you change anything unless your server make some changes you still have your configuration, ex. the headers
The best way is create a custom validate method using DataRequest extension:
func customValidate() -> Self {
return self.validate { _, response, data -> Request.ValidationResult in
guard (400...599) ~= response.statusCode else { return .success(()) }
guard let data = data else { return .failure(MyAppGeneralError.generalResponseError) }
guard let errorResponse = try? JSONDecoder().decode(MyAppResponseError.self, from: data) else {
return .failure(MyAppGeneralError.generalResponseError)
}
if response.statusCode == 401 {
return .failure(MyAppGeneralError.unauthorizedAccessError(errorResponse))
}
return .failure(MyAppGeneralError.responseError(errorResponse))
}
}
With a client with a generic function where the generic is decodable using our custom validate.
class APIClient {
var session: Session
init(session: Session = Session.default) {
self.session = session
}
#discardableResult
func performRequest<T: Decodable>(request: URLRequestConvertible,
decoder: JSONDecoder = JSONDecoder(),
completion: #escaping (Result<T, AFError>) -> Void) -> DataRequest {
return AF.request(request).customValidate().responseDecodable(decoder: decoder, completionHandler: { (response: DataResponse<T, AFError>) in
completion(response.result)
})
}
func getProfile(userID: Int, _ completion: #escaping (Result<UserToken, AFError>) -> Void) {
performRequest(request: APIRouter.profile(userID: userID), completion: completion)
}
}
using a router a:
enum APIRouter: URLRequestConvertible {
case profile(userId :Int)
static let baseURLString = "https://myserver.com"
var method: HTTPMethod {
switch self {
case .profile:
return .get
}
}
var path: String {
switch self {
case .profile(let userID):
return "profile/\(userID)"
}
}
var body: Parameters {
return [:]
}
// MARK: URLRequestConvertible
func asURLRequest() throws -> URLRequest {
let url = try APIRouter.baseURLString.asURL()
var urlRequest = URLRequest(url: url.appendingPathComponent(path))
urlRequest.httpMethod = method.rawValue
// Common Headers
urlRequest.setValue("application/json", forHTTPHeaderField: "Accept")
// Encode body
urlRequest = try JSONEncoding.default.encode(urlRequest, with: body)
return urlRequest
}
}
import Foundation
import UIKit
import Alamofire
import SwiftyJSON
class AFWrapper: NSObject {
static let sharedInstance = AFWrapper()
//TODO :-
/* Handle Time out request alamofire */
func requestGETURL(_ strURL: String, success:#escaping (JSON) -> Void, failure:#escaping (Error) -> Void)
{
Alamofire.request(strURL).responseJSON { (responseObject) -> Void in
//print(responseObject)
if responseObject.result.isSuccess {
let resJson = JSON(responseObject.result.value!)
//let title = resJson["title"].string
//print(title!)
success(resJson)
}
if responseObject.result.isFailure {
let error : Error = responseObject.result.error!
failure(error)
}
}
}
func requestPOSTURL(_ strURL : String, params : [String : AnyObject]?, headers : [String : String]?, success:#escaping (JSON) -> Void, failure:#escaping (Error) -> Void){
Alamofire.request(strURL, method: .post, parameters: params, encoding: JSONEncoding.default, headers: headers).responseJSON { (responseObject) -> Void in
//print(responseObject)
if responseObject.result.isSuccess {
let resJson = JSON(responseObject.result.value!)
success(resJson)
}
if responseObject.result.isFailure {
let error : Error = responseObject.result.error!
failure(error)
}
}
}
}
This is something I have been working on! Not finished yet but could solve your issue. you can upgrade it to whatever you want.
typealias
typealias Closure<T> = (T)->()
typealias JSON = [String: Any]
Extension
extension JSONDecoder{
func decode<T : Decodable>(_ model : T.Type,
result : #escaping Closure<T>) ->Closure<Data>{
return { data in
if let value = try? self.decode(model.self, from: data){
result(value)
}
}
}
Protocol
//MARK:- protocol APIResponseProtocol
protocol APIResponseProtocol{
func responseDecode<T: Decodable>(to modal : T.Type,
_ result : #escaping Closure<T>) -> APIResponseProtocol
func responseJSON(_ result : #escaping Closure<JSON>) -> APIResponseProtocol
func responseFailure(_ error :#escaping Closure<String>)
}
Request:
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 300 // seconds
configuration.timeoutIntervalForResource = 500
alamofireManager = Alamofire.SessionManager(configuration: configuration)
func getRequest(forAPI api: String, params: JSON) -> APIResponseProtocol {
let responseHandler = APIResponseHandler()
var parameters = params
parameters["token"] = preference.string(forKey: USER_ACCESS_TOKEN)
alamofireManager.request(api,
method: .get,
parameters: parameters,
encoding: URLEncoding.default,
headers: nil)
.responseJSON { (response) in
print("Å api : ",response.request?.url ?? ("\(api)\(params)"))
switch response.result{
case .success(let value):
let json = value as! JSON
let error = json.string("error")
guard error.isEmpty else{
responseHandler.handleSuccess(value: value,data: response.data ?? Data())
case .failure(let error):
responseHandler.handleFailure(value: error.localizedDescription)
}
}
return responseHandler
}
Response Hanlder:
class APIResponseHandler : APIResponseProtocol{
init(){
}
var jsonSeq : Closure<JSON>?
var dataSeq : Closure<Data>?
var errorSeq : Closure<String>?
func responseDecode<T>(to modal: T.Type, _ result: #escaping Closure<T>) -> APIResponseProtocol where T : Decodable {
let decoder = JSONDecoder()
self.dataSeq = decoder.decode(modal, result: result)
return self
}
func responseJSON(_ result: #escaping Closure<JSON>) -> APIResponseProtocol {
self.jsonSeq = result
return self
}
func responseFailure(_ error: #escaping Closure<String>) {
self.errorSeq = error
}
func handleSuccess(value : Any,data : Data){
if let jsonEscaping = self.jsonSeq{
jsonEscaping(value as! JSON)
}
if let dataEscaping = dataSeq{
dataEscaping(data)
}
}
func handleFailure(value : String){
self.errorSeq?(value)
}
}
USAGE:
self?.apiInteractor?
.getRequest(forAPI: "https://maps.googleapis.com/maps/api/directions/json",
params: [
"origin" : "\(pickUpLatitude),\(pickUpLongitude)",
"destination" :"\(dropLatitude),\(dropLongitude)",
"mode" : "driving",
"units" : "metric",
"sensor" : "true",
"key" : "\(UserDefaults.value(for: .google_api_key) ?? "")"
])
.responseDecode(to: GoogleGeocode.self, { [weak self] (googleGecode) in
guard let welf = self,
let route = googleGecode.routes.first,
let leg = route.legs.first else{return}
welf.tripDetailModel?.arrivalFromGoogle = leg.duration.text ?? ""
welf.drawRoute(forRoute: route)
welf.calculateETA()
})
.responseJSON({ (json) in
debugPrint(json.description)
})
.responseFailure({ (error) in
debug(print: error)
})
just part of code, but try
let req = Alamofire.request(url, method: .get, parameters: nil)
then you can handle response code by using
req.response?.statusCode
and handle response by for example
req.responseString(completionHandler: <#T##(DataResponse<String>) -> Void#>)
or
req.responseJSON(completionHandler: <#T##(DataResponse<Any>) -> Void#>)
you have good example here
I am quite new to Swift,
we have a API server where we need to pass HEADERS like this:
Content-Type:application/json
Session:fb4e7f9b-0f31-4709-
I was looking in Google, the most basic example uses HTTP url call (i.e GET,POST)
Do I need to use some third-party product to call REST API by providing headers?
I would appreciate if you provide some guideline and possibly some example articles where it's showing how do to REST API call by providing different type HEADERs.
By using Alamofire it's pretty simple.
let headers: HTTPHeaders = [
"Content-Type": "application/json",
"Session": "fb4e7f9b-0f31-4709-"
]
Alamofire.request("https://httpbin.org/headers", headers: headers).responseJSON { response in
//Parse or print your response.
}
By using urlRequest
let header: HTTPHeaders = [
"Content-Type": "application/json",
"Session": "fb4e7f9b-0f31-4709-"
]
var urlRequest = URLRequest(url: URL(string: "your request url goes here."), cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10)
urlRequest.allHTTPHeaderFields = header
urlRequest.httpMethod = //.get, .post, .put
URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if let error = error {
print(error)
} else if let data = data ,let responseCode = response as? HTTPURLResponse {
do {
// Parse your response here.
}
}
catch let parseJSONError {
print("error on parsing request to JSON : \(parseJSONError)")
}
}
}.resume()
import Foundation
let url = URL(string: "")!
var request = URLRequest(url: url)
request.allHTTPHeaderFields = [
"Content-Type": "application/json",
"Session": "fb4e7f9b-0f31-4709-"
]
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else { return }
guard let data = data, let response = response else { return }
// handle data
}.resume()
The URLRequest type documentation outlines all of the different properties you can set. You need to create a URLRequest instance and then use URLSession to perform the request.
with header fields
static func getFeed(Completion:#escaping(FeedElement)->Void)
{
let urlString = NSString(format: "https://api-nhdev.india.com/api/v1/feeds/1/10/Kondapur") as String
guard let url = URL(string:urlString)
else {
return
}
let header: HTTPHeaders = [
"Authorization": YOUR_AUTH_ID
]
let request = NSMutableURLRequest(url: url as URL)
request.allHTTPHeaderFields = header
request.httpMethod = "GET"
let session = URLSession.shared
let mData = session.dataTask(with: request as URLRequest) { (data, response, error) -> Void in
guard let dataResponse = data,
error == nil else {
print(error?.localizedDescription ?? "Response Error")
return }
do{
//here dataResponse received from a network request
let decoder = JSONDecoder()
let codabledata = try decoder.decode(FeedElement.self, from: dataResponse)
print(codabledata)
//Response result
Completion(codabledata)
} catch let parsingError {
print("Error", parsingError)
}
}
mData.resume()
}
}
extension URL
{
init(baseUrl: String, path: String, method: RequestMethod, params: [String:Any])
{
var components = URLComponents(string: baseUrl)!
components.path += path
switch method {
case .get,.delete:
var queryItems = components.queryItems ?? []
queryItems.append(contentsOf: params.map {
URLQueryItem(name: $0.key, value: String(describing: $0.value))
})
//print(queryItems)
components.queryItems = queryItems
default:
break
}
//print(components.url!)
self = components.url!
}
}
extension URLRequest
{
init(baseUrl: String, path: String, method: RequestMethod, params: [String:Any]) {
let url = URL(baseUrl: baseUrl, path: path, method: method, params: params)
//////////////My Code For Remove Percentages In URL//////////////////////////
let urlWithPath = url.absoluteString.removingPercentEncoding
let absoluteUrl = URL(string: urlWithPath!)!
self.init(url: absoluteUrl)
//self.init(url: url)
let util = AuthToken.shared()
httpMethod = method.rawValue
setValue("application/json", forHTTPHeaderField: "Content-Type")
if util.getToken() != "" {
setValue(util.getToken(), forHTTPHeaderField: "Token")
}
switch method {
case .post, .put:
httpBody = try! JSONSerialization.data(withJSONObject: params, options: [])
default:
break
}
}
}
struct NetworkManager {
static let environment : NetworkEnvironment = .qa
private init(){}
static let shared = NetworkManager();
private var baseURL = APIEndPoint.baseURL
func load(path:String, method:RequestMethod, params:[String:Any], completion:#escaping(_ responseObject:Data?, _ error:Error?, _ isSuccess: Bool?) -> Void) -> Void{
let reachability = Reachability()
if(reachability?.connection == .wifi || reachability?.connection == .cellular)
{
// Creating the URLRequest object
let request = URLRequest(baseUrl: baseURL, path: path, method: method, params: params)
let task = URLSession.shared.dataTask(with: request) {
(data, response, error) in
if error != nil
{
completion(nil, nil, false)
}
else
{
completion(data, nil, true)
}
}
task.resume()
}
else
{
completion(nil, nil, false)
}
}
}
// MARK: - get user list
func getUserList(param : [String:Any]) {
NetworkManager.shared.load(path: APIEndPoint.homeURL, method: .get, params: param) { (data, error, response) in
if response!
{
guard let responseData = data else {
return
}
do {
print(responseData)
let jsonData = try JSONSerialization.jsonObject(with: responseData, options: .mutableContainers)
print(jsonData)
let apiResponse = try JSONDecoder().decode(User.self, from: responseData)
self.userList = apiResponse
self.delegate?.getUserListCompleted()
}catch {
print(error)
}
}
}
}
// MARK : Declare base URL
enum NetworkEnvironment {
case qa
case production
}
class APIEndPoint {
// MARK : Returning base URL
static var environmentBaseURL : String {
switch NetworkManager.environment {
case .production: return ""
case .qa: return "https://www.jsonkeeper.com/"
}
}
// MARK : Finally seted base URL for the end point
static var baseURL: String {
return environmentBaseURL
}
// MARK : List of sub URLs
static let homeURL = "b/12D8"
}
import SystemConfiguration
import Foundation
public enum ReachabilityError: Error {
case FailedToCreateWithAddress(sockaddr_in)
case FailedToCreateWithHostname(String)
case UnableToSetCallback
case UnableToSetDispatchQueue
case UnableToGetInitialFlags
}
#available(*, unavailable, renamed: "Notification.Name.reachabilityChanged")
public let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification")
public extension Notification.Name {
static let reachabilityChanged = Notification.Name("reachabilityChanged")
}
public class Reachability {
public typealias NetworkReachable = (Reachability) -> ()
public typealias NetworkUnreachable = (Reachability) -> ()
#available(*, unavailable, renamed: "Connection")
public enum NetworkStatus: CustomStringConvertible {
case notReachable, reachableViaWiFi, reachableViaWWAN
public var description: String {
switch self {
case .reachableViaWWAN: return "Cellular"
case .reachableViaWiFi: return "WiFi"
case .notReachable: return "No Connection"
}
}
}
public enum Connection: CustomStringConvertible {
case none, wifi, cellular
public var description: String {
switch self {
case .cellular: return "Cellular"
case .wifi: return "WiFi"
case .none: return "No Connection"
}
}
}
public var whenReachable: NetworkReachable?
public var whenUnreachable: NetworkUnreachable?
#available(*, deprecated, renamed: "allowsCellularConnection")
public let reachableOnWWAN: Bool = true
/// Set to `false` to force Reachability.connection to .none when on cellular connection (default value `true`)
public var allowsCellularConnection: Bool
// The notification center on which "reachability changed" events are being posted
public var notificationCenter: NotificationCenter = NotificationCenter.default
#available(*, deprecated, renamed: "connection.description")
public var currentReachabilityString: String {
return "\(connection)"
}
#available(*, unavailable, renamed: "connection")
public var currentReachabilityStatus: Connection {
return connection
}
public var connection: Connection {
if flags == nil {
try? setReachabilityFlags()
}
switch flags?.connection {
case .none?, nil: return .none
case .cellular?: return allowsCellularConnection ? .cellular : .none
case .wifi?: return .wifi
}
}
fileprivate var isRunningOnDevice: Bool = {
#if targetEnvironment(simulator)
return false
#else
return true
#endif
}()
fileprivate var notifierRunning = false
fileprivate let reachabilityRef: SCNetworkReachability
fileprivate let reachabilitySerialQueue: DispatchQueue
fileprivate(set) var flags: SCNetworkReachabilityFlags? {
didSet {
guard flags != oldValue else { return }
reachabilityChanged()
}
}
required public init(reachabilityRef: SCNetworkReachability, queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil) {
self.allowsCellularConnection = true
self.reachabilityRef = reachabilityRef
self.reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability", qos: queueQoS, target: targetQueue)
}
public convenience init?(hostname: String, queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil) {
guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { return nil }
self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue)
}
public convenience init?(queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil) {
var zeroAddress = sockaddr()
zeroAddress.sa_len = UInt8(MemoryLayout<sockaddr>.size)
zeroAddress.sa_family = sa_family_t(AF_INET)
guard let ref = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress) else { return nil }
self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue)
}
deinit {
stopNotifier()
}
}
public extension Reachability {
// MARK: - *** Notifier methods ***
func startNotifier() throws {
guard !notifierRunning else { return }
let callback: SCNetworkReachabilityCallBack = { (reachability, flags, info) in
guard let info = info else { return }
let reachability = Unmanaged<Reachability>.fromOpaque(info).takeUnretainedValue()
reachability.flags = flags
}
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
context.info = UnsafeMutableRawPointer(Unmanaged<Reachability>.passUnretained(self).toOpaque())
if !SCNetworkReachabilitySetCallback(reachabilityRef, callback, &context) {
stopNotifier()
throw ReachabilityError.UnableToSetCallback
}
if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef, reachabilitySerialQueue) {
stopNotifier()
throw ReachabilityError.UnableToSetDispatchQueue
}
// Perform an initial check
try setReachabilityFlags()
notifierRunning = true
}
func stopNotifier() {
defer { notifierRunning = false }
SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil)
SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil)
}
// MARK: - *** Connection test methods ***
#available(*, deprecated, message: "Please use `connection != .none`")
var isReachable: Bool {
return connection != .none
}
#available(*, deprecated, message: "Please use `connection == .cellular`")
var isReachableViaWWAN: Bool {
// Check we're not on the simulator, we're REACHABLE and check we're on WWAN
return connection == .cellular
}
#available(*, deprecated, message: "Please use `connection == .wifi`")
var isReachableViaWiFi: Bool {
return connection == .wifi
}
var description: String {
guard let flags = flags else { return "unavailable flags" }
let W = isRunningOnDevice ? (flags.isOnWWANFlagSet ? "W" : "-") : "X"
let R = flags.isReachableFlagSet ? "R" : "-"
let c = flags.isConnectionRequiredFlagSet ? "c" : "-"
let t = flags.isTransientConnectionFlagSet ? "t" : "-"
let i = flags.isInterventionRequiredFlagSet ? "i" : "-"
let C = flags.isConnectionOnTrafficFlagSet ? "C" : "-"
let D = flags.isConnectionOnDemandFlagSet ? "D" : "-"
let l = flags.isLocalAddressFlagSet ? "l" : "-"
let d = flags.isDirectFlagSet ? "d" : "-"
return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)"
}
}
fileprivate extension Reachability {
func setReachabilityFlags() throws {
try reachabilitySerialQueue.sync { [unowned self] in
var flags = SCNetworkReachabilityFlags()
if !SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags) {
self.stopNotifier()
throw ReachabilityError.UnableToGetInitialFlags
}
self.flags = flags
}
}
func reachabilityChanged() {
let block = connection != .none ? whenReachable : whenUnreachable
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
block?(self)
self.notificationCenter.post(name: .reachabilityChanged, object: self)
}
}
}
extension SCNetworkReachabilityFlags {
typealias Connection = Reachability.Connection
var connection: Connection {
guard isReachableFlagSet else { return .none }
// If we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi
#if targetEnvironment(simulator)
return .wifi
#else
var connection = Connection.none
if !isConnectionRequiredFlagSet {
connection = .wifi
}
if isConnectionOnTrafficOrDemandFlagSet {
if !isInterventionRequiredFlagSet {
connection = .wifi
}
}
if isOnWWANFlagSet {
connection = .cellular
}
return connection
#endif
}
var isOnWWANFlagSet: Bool {
#if os(iOS)
return contains(.isWWAN)
#else
return false
#endif
}
var isReachableFlagSet: Bool {
return contains(.reachable)
}
var isConnectionRequiredFlagSet: Bool {
return contains(.connectionRequired)
}
var isInterventionRequiredFlagSet: Bool {
return contains(.interventionRequired)
}
var isConnectionOnTrafficFlagSet: Bool {
return contains(.connectionOnTraffic)
}
var isConnectionOnDemandFlagSet: Bool {
return contains(.connectionOnDemand)
}
var isConnectionOnTrafficOrDemandFlagSet: Bool {
return !intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty
}
var isTransientConnectionFlagSet: Bool {
return contains(.transientConnection)
}
var isLocalAddressFlagSet: Bool {
return contains(.isLocalAddress)
}
var isDirectFlagSet: Bool {
return contains(.isDirect)
}
var isConnectionRequiredAndTransientFlagSet: Bool {
return intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection]
}
}
import Foundation
// MARK : Declare API request type
enum RequestMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case delete = "DELETE"
}
How would I make this same API request through codables?
In my app, this function is repeated in every view that makes API calls.
func getOrders() {
DispatchQueue.main.async {
let spinningHUD = MBProgressHUD.showAdded(to: self.view, animated: true)
spinningHUD.isUserInteractionEnabled = false
let returnAccessToken: String? = UserDefaults.standard.object(forKey: "accessToken") as? String
let access = returnAccessToken!
let headers = [
"postman-token": "dded3e97-77a5-5632-93b7-dec77d26ba99",
"Authorization": "JWT \(access)"
]
let request = NSMutableURLRequest(url: NSURL(string: "https://somelink.com")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error!)
} else {
if let dataNew = data, let responseString = String(data: dataNew, encoding: .utf8) {
print("----- Orders -----")
print(responseString)
print("----------")
let dict = self.convertToDictionary(text: responseString)
print(dict?["results"] as Any)
guard let results = dict?["results"] as? NSArray else { return }
self.responseArray = (results) as! [HomeVCDataSource.JSONDictionary]
DispatchQueue.main.async {
spinningHUD.hide(animated: true)
self.tableView.reloadData()
}
}
}
})
dataTask.resume()
}
}
I would suggest to do the following
Create Base Service as below
import UIKit
import Foundation
enum MethodType: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case patch = "PATCH"
case delete = "DELETE"
}
class BaseService {
var session: URLSession!
// MARK: Rebuilt Methods
func FireGenericRequest<ResponseModel: Codable>(url: String, methodType: MethodType, headers: [String: String]?, completion: #escaping ((ResponseModel?) -> Void)) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
// Request Preparation
guard let serviceUrl = URL(string: url) else {
print("Error Building URL Object")
return
}
var request = URLRequest(url: serviceUrl)
request.httpMethod = methodType.rawValue
// Header Preparation
if let header = headers {
for (key, value) in header {
request.setValue(value, forHTTPHeaderField: key)
}
}
// Firing the request
session = URLSession(configuration: URLSessionConfiguration.default)
session.dataTask(with: request) { (data, response, error) in
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
if let data = data {
do {
guard let object = try? JSONDecoder().decode(ResponseModel.self , from: data) else {
print("Error Decoding Response Model Object")
return
}
DispatchQueue.main.async {
completion(object)
}
}
}
}.resume()
}
private func buildGenericParameterFrom<RequestModel: Codable>(model: RequestModel?) -> [String : AnyObject]? {
var object: [String : AnyObject] = [String : AnyObject]()
do {
if let dataFromObject = try? JSONEncoder().encode(model) {
object = try JSONSerialization.jsonObject(with: dataFromObject, options: []) as! [String : AnyObject]
}
} catch (let error) {
print("\nError Encoding Parameter Model Object \n \(error.localizedDescription)\n")
}
return object
}
}
the above class you may reuse it in different scenarios adding request object to it and passing any class you would like as long as you are conforming to Coddle protocol
Create Model Conforming to Coddle protocol
class ExampleModel: Codable {
var commentId : String?
var content : String?
//if your JSON keys are different than your property name
enum CodingKeys: String, CodingKey {
case commentId = "CommentId"
case content = "Content"
}
}
Create Service to the specific model with the endpoint constants subclassing to BaseService as below
class ExampleModelService: BaseService<ExampleModel/* or [ExampleModel]*/> {
func GetExampleModelList(completion: ((ExampleModel?)/* or [ExampleModel]*/ -> Void)?) {
super.FireRequestWithURLSession(url: /* url here */, methodType: /* method type here */, headers: /* headers here */) { (responseModel) in
completion?(responseModel)
}
}
}
Usage
class MyLocationsController: UIViewController {
// MARK: Properties
// better to have in base class for the controller
var exampleModelService: ExampleModelService = ExampleModelService()
// MARK: Life Cycle Methods
override func viewDidLoad() {
super.viewDidLoad()
exampleModelService.GetExampleModelList(completion: { [weak self] (response) in
// model available here
})
}
}
Basically, you need to conform Codable protocol in your model classes, for this you need to implement 2 methods, one for code your model and another for decode your model from JSON
func encode(to encoder: Encoder) throws
required convenience init(from decoder: Decoder) throws
After that you will be able to use JSONDecoder class provided by apple to decode your JSON, and return an array (if were the case) or an object of your model class.
class ExampleModel: Codable {
var commentId : String?
var content : String?
//if your JSON keys are different than your property name
enum CodingKeys: String, CodingKey {
case commentId = "CommentId"
case content = "Content"
}
}
Then using JSONDecoder you can get your model array like this
do {
var arrayOfOrders : [ExampleModel] = try JSONDecoder().decode([ExampleModel].self, from: dataNew)
}
catch {
}
First of all, I can recommend you to use this application -quicktype- for turning json file to class or struct (codable) whatever you want. enter link description here.
After that you can create a generic function to get any kind of codable class and return that as a response.
func taskHandler<T:Codable>(type: T.Type, useCache: Bool, urlRequest: URLRequest, completion: #escaping (Result<T, Error>) -> Void) {
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if let error = error {
print("error : \(error)")
}
if let data = data {
do {
let dataDecoded = try JSONDecoder().decode(T.self, from: data)
completion(.success(dataDecoded))
// if says use cache, let's store response data to cache
if useCache {
if let response = response as? HTTPURLResponse {
self.storeDataToCache(urlResponse: response, urlRequest: urlRequest, data: data)
}
}
} catch let error {
completion(.failure(error))
}
} else {
completion(.failure(SomeError))
}
}
task.resume()
}
I want to post a JSON object I create in my service class and pass to the networkService.
This is my network service, but i get an error of
Value of type '[String : Any]' has no member 'data'
on the line: let jsonData = json.data(using: .utf8, allowLossyConversion: false)!
func request(json: [String:Any]) {
let url = URL(string: urlString)!
let jsonData = json.data(using: .utf8, allowLossyConversion: false)!
var request = URLRequest(url: url)
request.httpMethod = HTTPMethod.post.rawValue
request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
Alamofire.request(request).responseJSON {
(response) in
print(response)
}
}
The idea being I pass in my JSON when i call the func via the func parameter.
This is the JSON object passed in:
func loginUser(data: Array<String>, deviceToken: String) {
// create JSON
let json = [ "login-email" : data[0],
"login-password" : data[1],
"login-secret" : "8A145C555C43FBA5",
"devicetoken" : deviceToken
]
networkManager.request(json: json)
}
Then I convert and send it to the API (urlString)
Any idea if/why this isnt working?
THanks
Updated revision:
func request(json: [String:Any]) {
let url = URL(string: urlString)!
do {
let jsonData = try JSONSerialization.data(withJSONObject: json, options:[])
var request = URLRequest(url: url)
request.httpMethod = HTTPMethod.post.rawValue
request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
Alamofire.request(request).responseJSON {
(response) in
print(response)
}
} catch {
print("Failed to serialise and send JSON")
}
}
update: added my code to make a call with completion question:
func sendLoginRequest() {
let userLogin = UserService.init(loginEmail: userEmail, loginPassword: userPassword, loginSecret: loginSecret, deviceToken: deviceToken)
networkService.logUserIn(request: userLogin) { (<#JSON?#>, <#NSError?#>) in
<#code#>
}
}
edit: Updated Payload Shot:
edit 2: mapping issue example:
init?(_ json: JSON) {
// Map API Key from top level
guard let apiKey = json["apikey"].string else { return nil }
// Map User at user level
guard let userDataArray = json["user"].array else {
fatalError("user data array NOT FOUND")
}
print("USER DATA IS \(userDataArray)")
// assign user
for child in userDataArray {
guard let userID = child["id"].int,
let userEmail = child["email"].string,
let lastName = child["lastname"].string,
let firstName = child["firstname"].string,
let company = child["company"].string,
let userImage = child["image"].string,
let jobTitle = child["jobtitle"].string
else { return nil
}
}
// Assign to model properties
self.apiKey = apiKey
self.userEmail = userEmail
self.lastName = lastName
self.firstName = firstName
self.company = company
self.userImage = userImage
self.jobTitle = jobTitle
self.userID = userID
}
I just show how I work with this.
You don't have to convert your parameters to JSON. It's code from Alamofire.
/// A dictionary of parameters to apply to a `URLRequest`.
public typealias Parameters = [String: Any]
Use this method instead of your:
Alamofire.request(url, method: method, parameters: parameters, encoding: encoding, headers: customHeaders)
Try this:
Instead of your request.httpBody = jsonData you can pass your json in parameters.
Your whole code will be:
func request(json: [String:Any]) {
Alamofire.request(urlString, method: .post, parameters: json, encoding: JSONEncoding.default).responseJSON {
(response) in
print(response)
}
}
If you are interested in my approach:
func makePick(request: MakePickRequest, completionHandler: #escaping APICompletionHandler) {
let parameters = request.converToParameters()
Alamofire.request(Endpoints.makePick, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseJSON { response in
self.handleResponse(response: response, completionHandler: completionHandler)
}
}
Request:
struct MakePickRequest: GeneralRequest {
let eventId: Int64
let sportId: String
let pickType: PickType
let betType: BetType
let amount: Int
func converToParameters() -> [String : String] {
return ["event_id": String(eventId), "sport_id": sportId,
"pick_type": pickType.rawValue, "bet_type": betType.rawValue,
"amount": String(amount)]
}
}
Structure with endpoints:
struct Endpoints {
// Development baseURL
static let baseURL = "http://myurl/"
private static let apiVersion = "api/v1/"
static var fullPath: String {
return "\(baseURL)\(apiVersion)"
}
// MARK: - User endpoints (POST)
static var login: String {
return "\(fullPath)users/login"
}
static var signUp: String {
return "\(fullPath)users/signup"
}
...
}
Outside of any class (but import SwiftyJSON is obligatory):
typealias APICompletionHandler = (_ data: JSON?, _ error: NSError?) -> Void
Handle response:
private func handleResponse(response: DataResponse<Any>, completionHandler: APICompletionHandler) {
self.printDebugInfo(response)
switch response.result {
case .success(let value):
self.handleJSON(data: value, handler: completionHandler)
case .failure(let error):
print(error)
completionHandler(nil, error as NSError?)
}
}
private func handleJSON(data: Any, handler: APICompletionHandler) {
let json = JSON(data)
let serverResponse = GeneralServerResponse(json)
if (serverResponse?.status == .ok) {
handler(serverResponse?.data, nil)
} else {
handler(nil, self.parseJsonWithErrors(json))
}
}
GeneralServerResponse (depends on your server API):
import SwiftyJSON
final class GeneralServerResponse {
let data: JSON
let status: Status
init?(_ json: JSON) {
guard let status = json["status"].int else {
return nil
}
self.status = Status(status)
self.data = json["data"]
}
enum Status {
case ok
case error
case unauthorized
init(_ input: Int) {
if input >= 200 && input < 400 {
self = .ok
} else if input == 403 {
self = .unauthorized
} else {
self = .error
}
}
}
}
My actual example of usage.
This is outside:
func +=<K, V> ( left: inout [K : V], right: [K : V]) { for (k, v) in right { left[k] = v } }
Example of request:
func makePick(request: MakePickRequest, completionHandler: #escaping APICompletionHandler) {
var parameters = ["auth_token": Preferences.getAuthToken()]
parameters += request.converToParameters()
manager.apiRequest(url: Endpoints.makePick, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseJSON { response in
self.handleResponse(response: response, completionHandler: completionHandler)
}
}
SessionManager extension to add headers for all requests:
extension SessionManager {
func apiRequest(url: URLConvertible, method: HTTPMethod, parameters: Parameters? = nil, encoding: ParameterEncoding, headers: HTTPHeaders? = nil) -> DataRequest {
var customHeaders: HTTPHeaders = ["api-key" : "1wFVerFztxzhgt"]
if let headers = headers {
customHeaders += headers
}
return request(url, method: method, parameters: parameters, encoding: encoding, headers: customHeaders)
}
}
In APIManager class:
private let manager: SessionManager
init() {
manager = Alamofire.SessionManager.default
}
Call example:
apiClient.makePick(request: request) { data, error in
if let error = error {
print(error.localizedDescription)
return
}
if let data = data {
// data is a JSON object, here you can parse it and create objects
}
}
Example of class:
import SwiftyJSON
final class MyClass {
let id: Int
let username: String
let parameter: Double
init?(_ json: JSON) {
guard let id = json["id"].int, let username = json["username"].string,
let parameter = json["parameter"].double else {
return nil
}
self.id = id
self.username = username
self.parameter = parameter
}
}