I need to send a request to a server and get a response.
I have the following URL:
http://192.168.200.10:9044/api/tables/?filter={"open_visible":true,"related":false}
For the query I'm using Alamofire. Here are some ways how I do it:
1)
class func getTables(completion: #escaping (_ response : DataResponse<TablesResponse>) -> Void) {
SVProgressHUD.show()
let getTablesPath = apiEntryPoint + "tables/?filter={\"open_visible\":true,\"related\":false}"
Alamofire.request(getTablesPath)
.validate()
.responseObject { (response: DataResponse<TablesResponse>) in
SVProgressHUD.dismiss()
completion(response)
}
}
I get the error: screenshot
2)
class func getTables(completion: #escaping (_ response : DataResponse<TablesResponse>) -> Void) {
SVProgressHUD.show()
let getTablesPath = apiEntryPoint + "tables/?filter={%22open_visible%22:true,%22related%22:false}"
Alamofire.request(getTablesPath)
.validate()
.responseObject { (response: DataResponse<TablesResponse>) in
SVProgressHUD.dismiss()
completion(response)
}
}
I get the error.
3)
class func getTables(completion: #escaping (_ response : DataResponse<TablesResponse>) -> Void) {
SVProgressHUD.show()
var getTablesPath = apiEntryPoint + "tables/?filter="
let jsonParameters = ["open_visible":true, "related":false]
if let json = try? JSONSerialization.data(withJSONObject: jsonParameters, options: []) {
if let content = String(data: json, encoding: .utf8) {
getTablesPath += content
}
}
Alamofire.request(getTablesPath)
.validate()
.responseObject { (response: DataResponse<TablesResponse>) in
SVProgressHUD.dismiss()
completion(response)
}
}
I get the error.
4)
class func getTables(completion: #escaping (_ response : DataResponse<TablesResponse>) -> Void) {
SVProgressHUD.show()
let getTablesPath = apiEntryPoint + "tables/"
Alamofire.request(getTablesPath, parameters: ["open_visible":true, "related":false])
.validate()
.responseObject { (response: DataResponse<TablesResponse>) in
SVProgressHUD.dismiss()
completion(response)
}
}
I got all tables. Without taking into account the desired parameters. It's bad.
QUESTION
As I can send a request for the server, considering the necessary parameters.
Try
let urlParameters = yourParametersString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
because you can't just pass { and } characters in URL
You can use request parameter for more user-friendly.
Put your all request data into NSDictionary and send it to the server.
let parameters: NSDictionary = [
"YOUR_KEY": YOUR_VALUE,
]
// Both calls are equivalent
Alamofire.request(YOUR_SERVER_ULR, method: .post, parameters: parameters, encoding: JSONEncoding.default)
Alamofire.request(YOUR_SERVER_ULR, method: .post, parameters: parameters, encoding: JSONEncoding(options: []))
Another way is that, if you are passing some special character into your question parameter then you have encoded the URL.
let URL = YOUR_FINAL_URL_TO_SERVER.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
You can check out Apple Document for more detail.
Related
func getCar(session: String, CarHandler: #escaping (Result<CarResponseModel, Error>) -> Void) {
let url = MAIN_URL + "/user/car"
AF.request(url, method: .post, encoding: JSONEncoding.default, headers: ["Content-Type":"application/json; charset=utf-8", "Accept":"application/json", "session":session])
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.responseDecodable(of: CarResponseModel.self) { (response) in
switch response.result {
case .success(let response):
CarHandler(.success(response))
case let .failure(error):
CarHandler(error.localizedDescription)
}
}
}
func getHouse(session: String, HouseHandler: #escaping (Result<HouseResponseModel, Error>) -> Void) {
let url = MAIN_URL + "/user/house"
let parameters = ["name": "houseName"]
AF.request(url, method: .get, parameters: parameters, encoding: URLEncoding.queryString, headers: ["Content-Type":"application/json; charset=utf-8", "Accept":"application/json", "session":session])
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.responseDecodable(of: HouseResponseModel.self) { (response) in
switch response.result {
case .success(let response):
HouseHandler(.success(response))
case let .failure(error):
HouseHandler(.failure(error))
}
}
}
Question
Use two functions to get car and house information. I wonder if it's possible to put this together.
Reason
The idea that if I receive information using only one communication function, I can handle the error at once.
My idea
The parameters or get, post information for handing over to the server are made by another function using communication and handed over to one communication function
Desired result
func getData(request, AllHandler: #escaping (Result<AnyModelStruct, Error>) -> Void) {
AF.request(request)
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.responseDecodable(of: AnyModelStruct.self) { (response) in
switch response.result {
case .success(let response):
AllHandler(.success(response))
case let .failure(error):
AllHandler(.failure(error))
}
}
}
Problem
func getData(request, AllHandler: #escaping (Result<HouseResponseModel, Error>) -> Void) {
AF.request(request)
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.responseDecodable(of: HouseResponseModel.self) { (response) in
switch response.result {
case .success(let response):
AllHandler(.success(response))
case let .failure(error):
AllHandler(.failure(error))
}
}
}
...
let url = URL(string: "http://www.stackoverflow.com")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
I know that url, method, parameters, encoding, header information can be generated and handed over as a request,
I don't know how to give and receive a model...
Model information is required for escape and response decoding. Is there a way to hand over the model information here? Or is it possible to set the Any type?
Others
Lastly, I would like to know a tip on how to proceed when I perform many API requests in other projects, if the method I'm trying now is not a popular method.
1. First, you can create a base service to generate AF requests
import Foundation
import Alamofire
class BaseService {
public static let shared = BaseService()
private var request = URLRequest(url: URL(string: Constant.API_BASE_URL)!)
private init() {}
public func generateRequest(url: String, method: HTTPMethod, body: Data?) -> URLRequest {
guard let formateUrl = URL(string: url) else {
fatalError("Invalid URL")
}
request.url = formateUrl
request.method = method
if let body_data = body {
request.httpBody = body_data
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
return request
}
}
2. You need to create a generic method for calling API requests
import Foundation
import Alamofire
struct API {
private static let decoder = JSONDecoder()
static func getRequest<T: Decodable>(urlPath: String, model: T.Type, completed: #escaping(Result<T, AFError>)-> Void) {
let request = BaseService.shared.generateRequest(url: urlPath, method: .get, body: nil)
AF.request(request).validate().responseDecodable(of: model.self, decoder: decoder) { (response) in
switch response.result {
case .success(let result):
completed(.success(result))
case .failure(let error):
completed(.failure(error))
}
}
}
3. After that, You can call the previous method anywhere with any JSON model
API.getRequest(urlPath: "YOUR API PATH", model: HouseResponseModel.self) { result in
switch result {
case .success(let res):
print(res)
case .failure(let error):
print(error)
}
}
I am using Almofire for all my requests and works fine. I need to know how to generalize all requests to handle all errors at one place.
func updateSettingValue(group : String , value: String , callback: #escaping (SettingsResponseModel) -> Void, errorCallback: #escaping (Error) ->Void)
{
let url = BASE_URL_PROD + API_SETTINGS
let settingsParams : Parameters = ["Setting" : group , "Tag" : value]
Alamofire.request(url, method: .put, parameters: settingsParams, headers: getHeader()).responseObject {
(response: DataResponse< SettingsResponseModel>) in
switch response.result {
case .success:
print("response \(response)")
DispatchQueue.main.async {
callback(response.result.value!)
}
break
case .failure(let error):
print(error)
errorCallback(error)
}
}
}
func releaseKeys(mKey: String ,callback: #escaping (ReleaseKeyModel) -> Void
, errorCallback: #escaping (Error) -> Void){
let url = BASE_URL_PROD + API_RELEASE_KEY
let params: Parameters = ["mKey" : mKey]
Alamofire.request(url, method: .delete, parameters: params, encoding: URLEncoding.default, headers: getHeader()).responseObject{
(response : DataResponse< ReleaseKeyModel >) in
print("releaseKey: \(response) ")
switch response.result {
case .success:
DispatchQueue.main.async {
callback(response.result.value!)
}
break
case .failure(let error):
print(error)
errorCallback(error)
}
}
}
How can I generalize this to take parameters for Mapping Model class in DataResponse so that I don't have to handle success and failure case individually for all methods.
You can divide the work with the server into 2 classes:
1) class RestClient
import Foundation
typealias responseBlock = (_ swiftObj: Any?, _ error: Error?) -> Void
class RestClient: NSObject {
static let shared = RestClient()
private var http = HttpService()
func updateSettingValue(group: String, value: String, resp: #escaping responseBlock) {
let url = BASE_URL_PROD + API_SETTINGS
let params = ["Setting": group, "Tag": value]
http.reque(url, method: .put, parameters: params, headers: getHeader(), resp: { (value, error) in
if let err = error {
return resp(nil, err)
}
guard let data = value else {
return resp(nil, error)
}
//your method for parse data
self.parseData(respData: data,
modelCls: SettingsResponseModel.self,
response: resp)
})
}
func releaseKeys(mKey: String, resp: #escaping responseBlock) {
let url = BASE_URL_PROD + API_RELEASE_KEY
let params = ["mKey": mKey]
http.reque(url, method: .delete, parameters: params, encoding: URLEncoding.default, headers: getHeader(), resp: { (value, error) in
if let err = error {
return resp(nil, err)
}
guard let data = value else {
return resp(nil, error)
}
//your method for parse data
self.parseData(respData: data,
modelCls: ReleaseKeyModel.self,
response: resp)
})
}
}
2) class HttpService
class HttpService {
func reque(_ url: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil,
queue: QueueQos = .defaultQos,
resp: #escaping responseBlock) {
Alamofire.request(url,
method: method,
parameters: parameters,
encoding: encoding,
headers: headers
).responseObject (queue: queue) { (response) in
switch response.result {
case .success:
if let jsonResp = response.result.value {
//You can also check out some error messages at this place.
resp(jsonResp, nil)
}
case .failure(let error):
resp(nil, error)
}
}
}
}
Try this, using generics
func releaseKeys<T: Codable>(parameters params: [String: Any], callback: #escaping (T) -> Void
, errorCallback: #escaping (Error) -> Void){
Alamofire.request(url, method: .delete, parameters: params, encoding: URLEncoding.default, headers: getHeader()).responseObject{
(response : DataResponse< T >) in
print("releaseKey: \(response) ")
switch response.result {
case .success:
DispatchQueue.main.async {
callback(response.result.value!)
}
break
case .failure(let error):
print(error)
errorCallback(error)
}
}
}
I am calling my webservice in this way.
public func prepareUrl (baseUrl: String, appendString: String, bindedParams: String, isAuthorized: Bool, method: HTTPMethod, jsonBody: [String:String], callback: #escaping (String) ->Void> Void)
{
let dm=Datamanager.sharedInstance
let baseUrl=dm.globalUrl
let urlString=baseUrl!+appendString as String+bindedParams as String
print(urlString)
Alamofire.request(urlString, method: method, parameters: nil, encoding: JSONEncoding.default)
.downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
print("Progress: \(progress.fractionCompleted)")
}
.validate { request, response, data in
// Custom evaluation closure now includes data (allows you to parse data to dig out error messages if necessary)
return .success
}
.responseJSON { response in
debugPrint(response)
callback("success")
}
}
But how can I do error handling here. Even If I referred the github Alamofire 4.0 migration I don't have clear idea how to do it.
Please help me.
thanks
In Alamofire gitHub has the method:
You don't need the validate, Alamofire default validation is that 200 to 299 is success, you can get the error from the response.
public func prepareUrl (baseUrl: String, appendString: String, bindedParams: String, isAuthorized: Bool, method: HTTPMethod, jsonBody: [String:String], callback: #escaping (String) ->Void> Void)
{
let dm=Datamanager.sharedInstance
let baseUrl=dm.globalUrl
let urlString=baseUrl!+appendString as String+bindedParams as String
print(urlString)
Alamofire.request(urlString, method: method, parameters: nil, encoding: JSONEncoding.default)
.downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
print("Progress: \(progress.fractionCompleted)")
}
.responseJSON { response in
debugPrint(response)
switch response.result {
case .success:
callback("success")
case .failure(let error):
print(error)
}
}
}
Checkout the documentation
I use Alamofire for my request in this function and I have this error if someone could help me please thank in advance.
Expression type 'DataRequest' is ambiguous without more context
func report(_ track: Track, completionHandler: #escaping (_ error: NSError?) -> Void) {
var headers:[String:String] = [:]
if AuthManager.defaultAuthManager().isLoggedIn() {
headers = ["Authorization": "Bearer " + AuthManager.defaultAuthManager().authToken.token!]
}
let params: [String: Any] = ["trackCode": track.code]
let urlString = Cizoo.APIBaseUrl + CizooScheme.report
CizooAPI.manager.request(urlString, method: .post, parameters: params, encoding: .JSONEncoding.default, headers: headers as HTTPHeaders)
.validate()
.responseJSON(completionHandler: { // Error at this line
response in
switch response.result {
case .success:
completionHandler(error: nil)
case .failure(let error):
completionHandler(error: error)
}
})
}
From the Alamofire source code you can see that the declaration of the method is:
#discardableResult
open func request(
_ url: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil)
-> DataRequest
{
var originalRequest: URLRequest?
do {
originalRequest = try URLRequest(url: url, method: method, headers: headers)
let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters)
return request(encodedURLRequest)
} catch {
return request(originalRequest, failedWith: error)
}
}
Which in your case probably the urlString is not conforming to the URLConvertible protocol.
Thanks !
Finally, It was a concern of type declaration
Here is the code solved :
func report(_ track: Track, completionHandler: #escaping (_ error: NSError?) -> Void) {
var headers:HTTPHeaders? = [:]
let params:Parameters? = ["trackCode": track.code]
let encoding:ParameterEncoding = JSONEncoding.default
let method:HTTPMethod = .post
let urlString:URLConvertible = (Cizoo.APIBaseUrl + CizooScheme.report)
if AuthManager.defaultAuthManager().isLoggedIn() {
headers = ["Authorization": "Bearer " + AuthManager.defaultAuthManager().authToken.token!]
}
CizooAPI.manager.request(urlString, method: method, parameters: params, encoding: encoding, headers: headers)
.validate()
.responseJSON(completionHandler: {
response in
switch response.result {
case .success:
completionHandler(nil)
case .failure(let error):
completionHandler(error as NSError?)
}
})
}
I have began to refactor my Alamofire api calls to keep them in a seperate file. The only problem is that I dont know how to return the statuscode as well.
Api file:
static func getCategories(_ catId: Int, response: #escaping (JSON) -> ()) {
let urlString = baseURL + ResourcePath.categories(catId: catId).description
Alamofire.request(urlString, encoding: JSONEncoding.default, headers: headers).responseJSON{ (responseData) -> Void in
let cCatData = JSON(responseData.result.value ?? [])
response(cCatData)
}
}
Then in my VC:
Api.getCategories(catId) { (JSON) -> () in
self.categories = JSON["catData"]
self.tableView.reloadData()
}
But I need to know if the status code is 200/400/404/422/500 and I dont want to use the .validate() function, I want to pass the status code
Normally if I would have everything in the same file I would get the status code by:
Alamofire.request("https://www.something", parameters: parameters, encoding: JSONEncoding.default, headers: headers)
.responseJSON() { response in
if let statusCode = response.response?.statusCode {
if statusCode == 200 {
}
}
If you want your closure to pass back the status code, then add an Int? parameter and pass it back:
static func getCategories(_ catId: Int, response: #escaping (JSON, Int?) -> ()) {
let urlString = baseURL + ResourcePath.categories(catId: catId).description
Alamofire.request(urlString, encoding: JSONEncoding.default, headers: headers).responseJSON { responseData in
let cCatData = JSON(responseData.result.value ?? [])
response(cCatData, responseData.response?.statusCode)
}
}
Or I might use more standard variable/parameter names:
static func getCategories(_ catId: Int, completionHandler: #escaping (JSON, Int?) -> ()) {
let urlString = baseURL + ResourcePath.categories(catId: catId).description
Alamofire.request(urlString, encoding: JSONEncoding.default, headers: headers).responseJSON { response in
let cCatData = JSON(response.result.value ?? [])
completionHandler(cCatData, response.response?.statusCode)
}
}
Either way, you can then do:
Api.getCategories(catId) { json, statusCode in
guard statusCode == 200 else {
print("status code not 200! \(statusCode)")
return
}
// if you got here, the status code must have been 200
}
So basically you want is something like this:
func returnStatusCode(_ urlPath: String, parameters: [String: AnyObject]? = nil, headers: [String: String]? = nil, encoding: ParameterEncoding = URLEncoding.default, completion: #escaping (_ statusCode: Int, _ responseData: [String: AnyObject]?) -> Void) {
// you can omit 'encoding:' if you'll set it to default since it will be default by default. \o/
Alamofire.request(urlPath, method: .get, parameters: parameters, encoding: encoding, headers: headers).responseJSON { (response) in
if response.result.isSuccess, let returnObject = response.result.value as? [String: AnyObject], let statusCode = response.response?.statusCode {
// do something...
completion(statusCode, returnObject)
}
}
}
use the callback to handle the status code alone