iOS使用Swift创建通用的Alamofire请求 [英] iOS create generic Alamofire request using swift

查看:96
本文介绍了iOS使用Swift创建通用的Alamofire请求的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

最近,我开始使用swift学习iOS应用开发,因此我是新手。我想在Swift&中实现rest api调用发现我们可以使用 URLRequest 来实现这一目标。因此,我编写了通用方法来调用所有类型的rest api(例如 get,put,post ),如下所示。

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))
            }
        }
    }
}

这是我从 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)
    }
}

此代码无法处理来自服务器的错误响应,例如 401、404 等。因此,我要寻找的是将此api( URLRequest )请求转换为通用的 Alamofire 请求并进行错误处理,例如 401、404 等。我已经安装了 Alamofire 吊舱。是否有人开发了具有解码和解码功能的通用 Alamofire 请求方法错误处理

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?

预先感谢:)

推荐答案

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

这是我的版本(使用 Alamofire 5.0.2 ):

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))
            }
        })
    }
}

上面的类使用最新的Alamofire版本(截至2020年2月),该类几乎涵盖了所有HTTP方法,并可以选择以Application / JSON格式或常规格式发送数据。有了此类,您将获得很大的灵活性,它会自动将响应转换为您的Swift对象。

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.

请看它具有的此类init方法:

Look at the init method of this class it has:


  1. 数据:[String,Any] =在此,您将放置表单数据。

  1. data: [String,Any] = In this you will put your form data.

标头:[String:String] =在此,您可以发送要与请求一起发送的自定义标头

headers: [String:String] = In this you can send custom headers that you want to send along with the request

url =这里您可以指定完整的url,如果您已经在Class中定义了baseurl,则可以将其保留为空白。当您要使用第三方提供的REST服务时,它非常方便。注意:如果要填充网址,则下一个参数服务应该为nil

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 =这是NetworkClass本身定义的枚举。这些充当端点。查看init方法,如果url为nil但服务不是nil,则它将附加在基本url的末尾以构成完整的URL,将提供示例。

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.

方法:HTTPMethod =在这里,您可以指定请求应使用的HTTP方法。

method: HTTPMethod = here you can specify which HTTP Method the request should use.

isJSONRequest =默认情况下设置为true。

isJSONRequest = set to true by default. if you want to send normal request set it to false.

在init方法中,您还可以指定公共数据或您希望随每个请求发送的标头,例如您的应用程序版本号,iOS版本等

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

现在看一下execute方法:这是一个通用函数,如果响应成功,它将返回您选择的swift对象。如果无法将响应转换为swift对象,它将以字符串形式打印响应。如果响应代码不在200-299范围内,则将失败,并为您提供完整的调试说明,以获取详细信息。

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.

用法:

说我们有以下结构:

struct Post: Codable{
    let userId: Int
    let id: Int
    let title: String
    let body: String
}

请注意在NetworkClass https://jsonplaceholder.typicode中定义的基本URL。 com /

Note the base url defined in NetworkClass https://jsonplaceholder.typicode.com/

示例1:发送内容类型为Application / JSON的HTTP Post

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)
            }
        }

输出:

Service: posts 
data: ["userId": 1, "body": "bar", "title": "foo"]
Post(userId: 1, id: 101, title: "foo", body: "bar")




  1. HTTP 400请求

  1. HTTP 400 Request

NetworkCall(data:[ email: peter @ klaven],URL: https://reqres.in/api/login ,方法:.post,isJSONRequest:false).executeQuery( ){
(结果:结果)在
转换结果{
case .success(let post):
print(post)
case .failure(let error) :
打印(错误)
}
}

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) } }

输出:

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. 带有自定义标头

  1. with custom headers

NetworkCall(data:[ username: sahil.manchanda2 @ gmail.com],标题:[ custom-header-key: custom-header-value],网址: https://httpbin.org/post ,方法:.post).executeQuery(){(结果:结果)在
切换结果{
情况下.success(让数据):
打印(数据)
情况。失败(出现错误):
打印(错误)
}
}

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) } }

输出:

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))

在最后一个示例中,您最终可以看到typeMismatch,在executeQuery中传递[String:Any],但是由于Any不能确认可编码,因此我不得不使用String。

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.

这篇关于iOS使用Swift创建通用的Alamofire请求的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆