当前位置: 首页 > 知识库问答 >
问题:

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

匡旭东
2023-03-14

最近我开始学习iOS应用程序开发使用swft,所以我是新来的。我想在Swift中实现rest api call

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

这就是我如何从控制器中调用它的方法

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

我解码服务器响应在下面的模型

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等。我已经安装了AlamofirePOD。是否有人开发了通用的Alamofire解码请求方法

提前感谢:)

共有3个答案

柳灿
2023-03-14

我正在共享RESTAPI上错误处理的特定部分。它将在下面的块中解码,您可能可以将其用作参考。

正如您所见,获取代码并转换为枚举非常简单。Alamofire允许这样做,但这取决于您的库版本。有时取决于RESTAPI如何在内部处理错误,它们不能抛出代码,例如,如果它的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))
                                }
                            }
                        })
公西英叡
2023-03-14

我将EVReflection与alamofire结合使用,我认为这是最好的组合之一。

使用Alamofire的URL协议。

这就是我所遵循的。

仅供参考。

为您的所有endpoint创建enum,并确认enum到urlrequestconverable。

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


}

根据您的要求制作一个结构。

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

现在,让一个通用函数接受URLRequestConvertible并以闭包形式返回您的模型。像这样的。

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

现在制作一个通用函数,接受您的响应并为您处理错误。像这样的。

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.

        }
    }

现在终于,如何正确使用它??!事实上,现在它非常简单,只需要两行代码,您就可以开始了。

GenericApiCallForObject(router: Router.Login(["xyz":"xyz"])) { (response : GeneralModel) in
    print(response)
}

请注意,这将只工作,如果你得到对象的响应。如果有一个数组或字符串,你必须为它做单独的函数,程序与上面相同。只有在成功的情况下,您才会得到响应,否则HandleResseAnd ErrorForObject函数将自动为您处理它。此外,在上面的解释中可能缺少一些变量。

充小云
2023-03-14

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

以下是我的版本(使用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对象。

看看这个类的init方法,它有:

>

  • data:[String,Any]=在这里您将放置表单数据。

    Headers:[String: String]=在这种情况下,您可以发送要随请求一起发送的自定义标头

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

    service:services=它是在NetworkClass本身中定义的枚举。这些作为endpoint。在init方法中,如果url为nil,但服务不是nil,那么它将附加在基本url的末尾以生成完整的url,下面将提供一个示例。

    method:HTTPMethod=您可以在此处指定请求应使用的HTTP方法。

    isJSONRequest=默认情况下设置为true。如果要发送普通请求,请将其设置为false。

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

    现在看看执行方法:这是一个通用函数,如果响应成功,它将返回您选择的swft对象。它将在字符串中打印响应,以防它未能将响应转换为您的swft对象。如果响应代码不属于范围200-299,那么它将是一个失败,并为您提供详细信息的完整调试描述。

    用法:

    假设我们有以下结构:

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

    请注意NetworkClass中定义的基本urlhttps://jsonplaceholder.typicode.com/

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

    输出:

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

    HTTP 400请求

    NetworkCall(数据:[“电子邮件”:”peter@klaven“],url:https://reqres.in/api/login,方法:.post,isJSONRequest:false).executeQuery(){(结果:结果)在开关结果{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}
    

    带有自定义标题

    NetworkCall(数据:[“用户名”:“sahil。manchanda2@gmail.com],标题:[“自定义标题键”:“自定义标题值”],url:https://httpbin.org/post,方法:.post).executeQuery(){(结果:结果)在开关结果{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,我试图在执行查询中传递[String: any],但由于any不能确认可编码,我不得不使用String。

  •  类似资料:
    • 问题内容: 我正在尝试使用进行同步请求。我查看了Stackoverflow并发现了这个问题:使异步alamofire请求成为sync 我看到接受的答案用于使请求同步,但是我无法使其正常工作。这是我的简化代码: 有了这段代码,我在尝试制作时会出错。我收到的错误如下: 无法调用非功能类型“布尔”的值 我尝试使用许多使用完成的示例来同步获取值(因为我需要先检索数据才能将其显示在表上,并同时获取该表的行数

    • 问题内容: 我有一个简单的放置请求,并且正在使用Alamofire的类型将数据发送到服务器。我想使用codable。如何将我的可编码结构转换为参数,或者重新配置Alamofire请求以将JSON对象作为参数?使用Alamofire发送帖子和放置请求的最佳,最有效的方法是什么? 这就是我现在正在使用Alamofire所做的事情。 这是我们的结构。 问题答案: 您可以制作一个新的并设置为您的编码。试试

    • 本文向大家介绍Swift网络请求库Alamofire使用详解,包括了Swift网络请求库Alamofire使用详解的使用技巧和注意事项,需要的朋友参考一下 前言 Alamofire是一个使用Swift开发的网络请求库,其开发团队是AFNetworking的原团队。它语法简洁,采用链式编程的思想,使用起来是相当的舒服。本质是基于NSURLSession进行封装。接下开我们就进入实战,开始学习Alam

    • 我做了我的项目与图像上传到服务器使用PHP脚本。它在Swift 2和alamofire上运行良好。 用alamofire 4更新到Swift 4后,上传代码不起作用。 以上是我更新的代码。请建议我应该怎么做来解决这个错误。或提供我改变这个图像上传使用PHP脚本的解决方案。

    • 背景:我的一个同事创建了一个3D可视化应用程序,作为一个用于android和windows桌面的libgdx项目。它可以被编译为使用RoboVM在iOS上运行。但是,我希望使用XCode在它周围包装额外的本机用户界面元素。我知道通过RoboVM以编程方式构建用户界面是可能的,但我很想研究一下是否有可能将现有的工作带入Xcode。我不需要编辑3D可视化组件,但添加额外的GUI元素周围的3D Vis窗

    • 本文向大家介绍iOS 如何使用UIBezierPath创建简单的形状,包括了iOS 如何使用UIBezierPath创建简单的形状的使用技巧和注意事项,需要的朋友参考一下 示例 对于一个简单的圈子: 迅速: 对于一个简单的矩形: 迅速: 对于简单的一行: 迅速: 半圈: 迅速: 对于一个简单的三角形: 迅速: