Generic Encodable could not be inferred - ios

I have an API class with the following function:
static func JSONPostRequest<EncodableType:Codable, DecodableType:Codable>(endpoint: String, jsonData: EncodableType, callback: #escaping (DecodableType) -> Void, clientErrorCallback: #escaping (Error) -> Void, responseErrorCallback: #escaping (URLResponse) -> Void, requestHeaders: [String: String]?) {
// Encoding the data into JSON
var jsonData: Data = Data();
do {
jsonData = try JSONEncoder().encode(jsonData)
}
catch {
print("JSON Encode")
return ;
}
// Setup the request
var request: URLRequest = URLRequest(url: URL(string: self.endpoint + self.apiVersion + "/" + endpoint)!)
request.httpMethod = "POST"
request.httpBody = jsonData
// Set the custom headers
if(requestHeaders != nil) {
for (headerKey, headerValue) in requestHeaders! {
request.setValue(headerValue, forHTTPHeaderField: headerKey)
}
}
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
// Was there an error in request?
if error != nil {
clientErrorCallback(error!)
return
}
// Response code is 2XX?
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
responseErrorCallback(response!)
return
}
// Has mime type fine?
guard let mime = response!.mimeType, mime == "application/json" else {
print("Wrong MIME type!")
return
}
let decoder = JSONDecoder()
let loginResponse = try! decoder.decode(DecodableType.self, from: data!)
callback(loginResponse)
}
task.resume()
}
But when I would like to call it from my login view ... :
API.JSONPostRequest(
endpoint: "login",
jsonData: LoginRequest(username: self.username, password: self.password),
callback: { loginResponse in
// success callback
if loginResponse.success && loginResponse.token != "" {
callback(loginResponse)
}
else {
// backend logic error
}
}, clientErrorCallback: { error in
// client error
}, responseErrorCallback: { urlResponse in
// response error
}, requestHeaders: headers
)
... I get the following error message:
Generic parameter 'EncodableType' could not be inferred
Here's my LoginRequest implementation:
struct LoginRequest: Codable {
var username: String;
var password: String;
}
The error message shows near the first line of the function call, near "API.JSONPostRequest(".
What could be the problem here?

You didn't provide a type for loginResponse, and there's no way from this code to guess what it is. Looking at the code you've posted, I can't figure out what the response is supposed to be (nothing you've posted here has a .success or .token value).
I assume you meant to add the following code:
struct LoginResponse: Codable {
var success: Bool
var token: String
}
And I assume somewhere there is a variable callback of type (LoginResponse) -> Void.
With that type, you'll still need to let the compiler know it's what you expect. It has no way to guess of the infinite number of types this could return, that you want LoginResponse:
...
callback: { (loginResponse: LoginResponse) in
...
Alternately, you can adjust your type signature to pass the expected type, for example:
static func JSONPostRequest<Request, Response>(endpoint: String,
jsonData: Request,
returning: Response.Type = Response.self,
callback: #escaping (Response) -> Void,
clientErrorCallback: #escaping (Error) -> Void,
responseErrorCallback: #escaping (URLResponse) -> Void,
requestHeaders: [String: String]?)
where Request: Encodable, Response: Decodable {
This would allow you to pass returning: LoginResponse.self, which can be nicer than embedding it in the closure:
...
returning: LoginResponse.self,
callback: { loginResponse in
...
(That said, what I'm discussing here fixes the fact that DecodableType is actually ambiguous. The fact that you're getting an error on EncodableType suggests that you have other type-mismatches. I recommend simplifying this to a minimal example that actually demonstrates the problem in a playground. As you've written it, I had to guess at a bunch of extra code.)

Related

iOS create generic Alamofire request using swift

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

Generic parameter 'T' could not be inferred while calling a method

I am getting below error
Generic parameter 'T' could not be inferred
I have created a method and when I am trying to call that method then getting that error. I am adding both methods below.
func requestNew<T> ( _ request: URLRequest, completion: #escaping( Result< T , NetworkError>) -> Void ) where T : Decodable {
URLCache.shared.removeAllCachedResponses()
print("URL \((request.url as AnyObject).absoluteString ?? "nil")")
//use the currentrequest for cancel or resume alamofire request
currentAlamofireRequest = self.sessionManager.request(request).responseJSON { response in
//validate(statusCode: 200..<300)
if response.error != nil {
var networkError : NetworkError = NetworkError()
networkError.statusCode = response.response?.statusCode
if response.response?.statusCode == nil{
let error = (response.error! as NSError)
networkError.statusCode = error.code
}
//Save check to get the internet connection is on or not
if self.reachabilityManager?.isReachable == false {
networkError.statusCode = Int(CFNetworkErrors.cfurlErrorNotConnectedToInternet.rawValue)
}
completion(.failure(networkError))
}else{
print("response --- > ",String(data: response.data!, encoding: .utf8) ?? "No Data found")
if let responseObject = try? JSONDecoder().decode(T.self, from: response.data!) {
completion(.success(responseObject.self))
}else {
}
}
}
}
Below is screenshot of error
![
]1
func getVersion1(complete :#escaping (Response<Version>) -> Void, failure:#escaping onFailure) {
self.network.requestNew(self.httpRequest) { (result) in
print("hello")
}
When Swift cannot infer the generic parameter, although it accepts the generic declaration of the method, you can specify the type by passing type fixed parameters.
Try this:
func getVersion1(complete :#escaping (Response<Version>) -> Void, failure:#escaping onFailure) {
self.network.requestNew(self.httpRequest) { (result: Result<Version, NetworkError>) in
print("hello")
}
}
You may need to change Result<Version, NetworkError> to Result<SomeDecodableType, NetworkError>, if Version is not the type you expect from the request.

Contextual closure type error when accessing the class file in swift

I am new to swift programming, i have Implemented Speech to text using Microsoft Azure,when i calling the class file i am getting the error like "Contextual closure type '(Data?, URLResponse?, Error?) -> Void' expects 3 arguments, but 1 was used in closure body " .can anyone help me to solve this error.
//This is the sample code where i am calling the function in class file
TTSHttpRequest.submit(withUrl: TTSSynthesizer.ttsServiceUri,
andHeaders: [
"Content-Type": "application/ssml+xml",
"X-Microsoft-OutputFormat": outputFormat.rawValue,
"Authorization": "Bearer " + accessToken,
"X-Search-AppId": appId,
"X-Search-ClientID": clientId,
"User-Agent": "TTSiOS",
"Accept": "*/*",
"content-length": "\(message.lengthOfBytes(using: encoding))"
],
andBody: message.data(using: encoding)) { (c: TTSHttpRequest.Callback) in
guard let data = c.data else { return }
callback(data)
}
//This is the class file where i am getting the error
class TTSHttpRequest {
typealias Callback = (data: Data?, response: URLResponse?, error: Error?)
static func submit(withUrl url: String, andHeaders headers: [String: String]? = nil, andBody body: Data? = nil, _ callback: #escaping (Callback) -> ()) {
guard let url = URL(string: url) else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
headers?.forEach({ (header: (key: String, value: String)) in
request.setValue(header.value, forHTTPHeaderField: header.key)
})
if let body = body {
request.httpBody = body
}
let task = URLSession.shared.dataTask(with: request) { (c:Callback) in //In this line i am getting above mentioned error.
callback(c)
}
task.resume()
}
}
As Leo Dabus commented, you cannot pass a single argument closure (your closure takes one argument c of type Callback) as a parameter expecting three-argument closure.
This is the effect of SE-0110 Distinguish between single-tuple and multiple-argument function types.
The status of the proposal currently shows as Deferred, but the most functionality of this proposal is already implemented and effective in Swift 4, and only a little part (including Addressing the SE-0110 usability regression in Swift 4) is rewinded and under re-designing.
One possible fix would be something like this:
class TTSHttpRequest {
typealias Callback = (data: Data?, response: URLResponse?, error: Error?)
static func submit(withUrl url: String, andHeaders headers: [String: String]? = nil, andBody body: Data? = nil, _ callback: #escaping (Callback) -> ()) {
guard let url = URL(string: url) else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
headers?.forEach({ (header: (key: String, value: String)) in
request.setValue(header.value, forHTTPHeaderField: header.key)
})
if let body = body {
request.httpBody = body
}
let task = URLSession.shared.dataTask(with: request) { data, response, error in //<- three arguments
callback((data, response, error)) //<- Call the callback with one tuple.
}
task.resume()
}
}

How do I set up a success/failure block so that I can pass a string into it using Swift?

I have an API method that returns teams for particular leagues. All I need to do is pass in some parameters.
For example, the API URL looks like this: http://api.website.com/api/teams?season=last&league=0Xc334xUK4
Here is my code:
#objc class APITeam: NSObject {
var leagueObjectID: NSString!
let baseUrl = "http://api.website.com/api"
static let sharedInstance = APITeam()
static let getTeamsEndpoint = "/teams"
static let params = "?season=last&league="
private override init() {}
func getTeams (_ onSuccess: #escaping(Any) -> Void, onFailure: #escaping(Error) -> Void) {
var request = URLRequest(url: URL(string: baseUrl + APITeam.getTeamsEndpoint + APITeam.params)!)
let session = URLSession.shared
request.httpMethod = "GET"
let task = session.dataTask(with: request) { (data : Data?, response : URLResponse?, error : Error?) in
if(error != nil){
onFailure(error!)
} else{
do {
let result = try JSONSerialization.jsonObject(with: data!)
onSuccess(result)
} catch {
print(error)
}
}
}
task.resume()
}
}
In order for this API method to return data, I need to pass in the objectID of the league I want teams for when making a request.
I've tried to add parameter to:
func getTeams (_ onSuccess: #escaping(Any) -> Void, onFailure: #escaping(Error) -> Void, leagueObjectID: String) {
var request = URLRequest(url: URL(string: baseUrl + APITeam.getTeamsEndpoint + APITeam.params + leagueObjectID)!)
This doesn't work as expected. When I use the method in another class, "leagueObjectID" acts as an additional part of the success, error block if that makes sense. I need to be able to pass the leagueObjectID into the method so it's used at the end of the URL the request is made to.
This is how I call APITeam in an objective-c class:
[[APITeam sharedInstance] getTeams:^(id result) {
} onFailure:^(NSError * error) {
}];
As you can see, the extra parameter I added to the getTeams function doesn't show up, and when I try to manually add it, I get an error.
How would you handle this?
An example would be much appreciated.
Thanks for your time
Change it to
func getTeams (leagueObjectID: String, onSuccess: #escaping(Any) -> Void, onFailure: #escaping(Error) -> Void)
static might not work as you're used to in other languages. static var behaves like class var.
If I understand you correctly, this would satisfy your needs
#objc class APITeam: NSObject {
private static let baseUrl = "http://api.website.com/api"
private static let getTeamsEndpoint = "/teams"
let params = "?season=last&league="
private override init() {}
func getTeam(id: String, _ onSuccess: #escaping(Any) -> Void, onFailure: #escaping(Error) -> Void) {
var request = URLRequest(url: URL(string: APITeam.baseUrl + APITeam.getTeamsEndpoint + APITeam.params + id)!)
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if let error = error {
onFailure(error)
} else if let responseData = data {
do {
let result = try JSONSerialization.jsonObject(with: responseData)
onSuccess(result)
} catch {
print(error)
}
}
}.resume()
}
}
Usage:
APITeam().getTeam(id: "0Xc334xUK4", ..your completion handlers here..)
This worked for me:
#objc class APITeam: NSObject {
var leagueObjectID: NSString!
let baseUrl = "http://api.website.com/api"
static let sharedInstance = APITeam()
static let getTeamsEndpoint = "/teams"
static let params = "?season=last&league="
private override init() {}
func getTeams (leagueObjectID: String, _ onSuccess: #escaping(Any) -> Void, onFailure: #escaping(Error) -> Void) {
var request = URLRequest(url: URL(string: baseUrl + APITeam.getTeamsEndpoint + APITeam.params + leagueObjectID)!)
let session = URLSession.shared
request.httpMethod = "GET"
let task = session.dataTask(with: request) { (data : Data?, response : URLResponse?, error : Error?) in
if(error != nil){
onFailure(error!)
} else{
do {
let result = try JSONSerialization.jsonObject(with: data!)
onSuccess(result)
} catch {
print(error)
}
}
}
task.resume()
}
}
Then in my Objective-C class:
[[APITeam sharedInstance] getTeamsWithLeagueObjectID:#"93PwHe5e4S" :^(id league) {
NSLog(#"THE LEAGUE: %#", league);
} onFailure:^(NSError * error) {
}];
It's also important to build the app before trying to access the original class. That was a vital step for me. Sometimes I don't need to do this, but this time around, I couldn't access the new changed APITeam getTeams function until I built the app.

Expression resolves to an unused function (Swift)

I am new to swift programming and need bit of your help forgive me if i am asking something stupid. I am trying call a function from my UIViewController for a POST request to API. Calling function is like this
#IBAction func actionStartSignIn(sender: AnyObject) {
let email: String = txtEmail.text!
let password: String = txtPassword.text!
if !email.isEmpty && !password.isEmpty && General.isValidEmail(email) && password.characters.count>6{
var request = RequestResponse()
request.email = email
request.password = password
let isValid = NSJSONSerialization.isValidJSONObject(request)
print(isValid)
var requestBody: String = ""
// Facing issue in following line
RequestManager.sharedInstance.postRequest(Constants.BASE_URL + Constants.LOGIN, body: requestBody, onCompletion: {(json: JSON) in{
let result = json["response"]
print(result)
}
}
)
}
}
And Called Function is like this
func postRequest(route: String, body: String, onCompletion: (JSON) -> Void) {
makeHTTPPostRequest(route, requestBody: body, onCompletion: { json, err in
onCompletion(json as JSON)
})
}
Further,
// MARK: Perform a POST Request
private func makeHTTPPostRequest(path: String, requestBody: String, onCompletion: ServiceResponse) {
let request = NSMutableURLRequest(URL: NSURL(string: path)!)
// Set the method to POST
request.HTTPMethod = "POST"
do {
// Set the POST body for the request
// let jsonBody = try NSJSONSerialization.dataWithJSONObject(body, options: .PrettyPrinted)
// request.HTTPBody = jsonBody
request.HTTPBody = requestBody.dataUsingEncoding(NSUTF8StringEncoding);
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
if let jsonData = data {
let json:JSON = JSON(data: jsonData)
onCompletion(json, nil)
} else {
onCompletion(nil, error)
}
})
task.resume()
}/* catch {
// error
onCompletion(nil, nil)
}*/
}
and
typealias ServiceResponse = (JSON, NSError?) -> Void
I am facing "Expression resolves to an unused function" while calling
RequestManager.sharedInstance.postRequest(Constants.BA‌​SE_URL + Constants.LOGIN, body: requestBody, onCompletion: {(json: JSON) in{ let result = json["response"] print(result) } } )
May be i am missing some basic syntax. Any help will be really appreciated.
Thank You.
Remove the { } phase after the in will solve the problem.
It should look like this:
RequestManager.sharedInstance.postRequest(Constants.BASE_URL + Constants.LOGIN, body: requestBody, onCompletion: {(json: JSON) in
let result = json["response"]
print(result)
}
)
For the closure param, you should not type it by yourself to prevent typo. Use tab key to select that param, then press enter, xCode will auto generate the code for you.
If use the way I just said, the trailing closure will look like this:
RequestManager.sharedInstance.postRequest(Constants.BASE_URL + Constants.LOGIN, body: requestBody) { (json) in
let result = json["response"]
print(result)
}

Resources