简体   繁体   English

iOS 使用 swift 创建通用的 Alamofire 请求

[英]iOS create generic Alamofire request using swift

Recently I have started learning iOS app development using swift so I am new to it.最近我开始使用 swift 学习 iOS 应用程序开发,所以我是新手。 I want to implement rest api call in swift & found that we can achieve this using URLRequest .我想在 swift 中实现 rest api 调用,发现我们可以使用URLRequest来实现这一点。 So I have written generic method to call all type(like get, put, post ) of rest api as below.所以我编写了generic method来调用rest api的所有类型(如get, put, post ),如下所示。

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这就是我从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.此代码无法处理来自服务器的401, 404等错误响应。 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.所以我所寻找的,是这个API(转换URLRequest )请求通用Alamofire请求错误处理像401, 404等。我已经安装Alamofire吊舱。 Is there anyone who has developed generic Alamofire request method with decoding & error handling ?是否有人开发了具有解码和错误处理功能的通用Alamofire请求方法?

Thanks in advance :)提前致谢 :)

Git link: https://github.com/sahilmanchanda2/wrapper-class-for-alamofire Git 链接: https : //github.com/sahilmanchanda2/wrapper-class-for-alamofire

Here is my version(Using Alamofire 5.0.2 ):这是我的版本(使用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.上述课程使用最新的 Alamofire 版本(截至 2020 年 2 月),该课程涵盖几乎所有 HTTP 方法,并可选择以应用程序/JSON 格式或普通格式发送数据。 With this class you get a lot of flexibility and it automatically converts response to your Swift Object.使用这个类,您可以获得很大的灵活性,它会自动将响应转换为您的 Swift 对象。

Look at the init method of this class it has:看看这个类的init方法,它有:

  1. data: [String,Any] = In this you will put your form data. data: [String,Any] = 在此您将放置表单数据。

  2. headers: [String:String] = In this you can send custom headers that you want to send along with the request headers: [String:String] = 在这个你可以发送你想和请求一起发送的自定义标头

  3. url = Here you can specify full url, you can leave it blank if you already have defined baseurl in Class. url = 这里可以指定完整的url,如果你已经在Class中定义了baseurl,可以留空。 it comes handy when you want to consume a REST service provided by a third party.当您想使用第三方提供的 REST 服务时,它会派上用场。 Note: if you are filling the url then you should the next parameter service should be nil注意:如果你正在填写 url 那么你应该下一个参数 service 应该是 nil

  4. service: services = It's an enum defined in the NetworkClass itself. service: services = 它是在 NetworkClass 本身中定义的枚举。 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.查看init方法,如果url为nil但service不为nil,那么它会在base url的末尾追加一个完整的URL,示例将提供。

  5. method: HTTPMethod = here you can specify which HTTP Method the request should use. method: HTTPMethod = 在这里您可以指定请求应该使用哪种 HTTP 方法。

  6. isJSONRequest = set to true by default. isJSONRequest = 默认设置为 true。 if you want to send normal request set it to false.如果您想发送普通请求,请将其设置为 false。

In the init method you can also specify common data or headers that you want to send with every request eg your application version number, iOS Version etc在 init 方法中,您还可以指定要随每个请求发送的通用数据或标头,例如您的应用程序版本号、iOS 版本等

Now Look at the execute method: it's a generic function which will return swift object of your choice if the response is success.现在看看 execute 方法:它是一个通用函数,如果响应成功,它将返回您选择的 swift 对象。 It will print the response in string in case it fails to convert response to your swift object.它将以字符串形式打印响应,以防它无法将响应转换为您的 swift 对象。 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.如果响应代码不在 200-299 范围内,那么它将失败并为您提供完整的调试描述以获取详细信息。

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/请注意 NetworkClass https://jsonplaceholder.typicode.com/ 中定义的基本 url

Example 1: Sending HTTP Post with content type Application/JSON示例 1:发送内容类型为 Application/JSON 的 HTTP Post

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")
  1. HTTP 400 Request HTTP 400 请求

    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) } } NetworkCall(data: ["email":"peter@klaven"], url: " https://reqres.in/api/login ", method: .post, isJSONRequest: false).executeQuery(){ (result: 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}
  1. 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) } } 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.在最后一个示例中,您可以在最后看到 typeMismatch,我尝试在 executeQuery 中传递 [String:Any] 但由于 Any 未确认为可编码,因此我不得不使用 String。

I use EVReflection with alamofire and i think this is one of the best combination to work with.我将 EVReflection 与 alamofire 一起使用,我认为这是最好的组合之一。

Use URLRequestConvertible protocol of Alamofire.使用 Alamofire 的 URLRequestConvertible 协议。

This is what i follow.这就是我所遵循的。

Just for reference purpose.仅供参考。

Make enum for your all endpoint and confirm that enum to URLRequestConvertible.为您的所有端点创建枚举并将该枚举确认为 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.现在创建一个通用函数,它接受 URLRequestConvertible 并在闭包中返回您的模型。 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.如果成功,您只会得到响应,否则 HandleResponseWithErrorForObject 函数会自动为您处理。 Also, some variables might be missing in above explanation.此外,上述解释中可能缺少一些变量。

I'm sharing a specific part for error handling on my REST api.我正在分享我的 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. Alamofire 允许这样做,但这取决于您的库版本。 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.有时取决于你的 REST api 如何在内部处理错误,它们不能抛出代码,例如如果它的 Java 后端,它们可以封装异常。

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.您可能需要这个使用 alamofilre 会话管理器来执行请求的函数。 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.您还可以将 cookie ant 标头等设置到此会话管理器,以便您将它们用于其余的请求。

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:最好的方法是使用 DataRequest 扩展创建自定义验证方法:

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:使用路由器 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导入 UIKit 导入 Alamofire 导入 SwiftyJSON

class AFWrapper: NSObject {类 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协议//MARK:- 协议 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{类 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在这里有很好的例子

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM