iOS 使用 swift 创建通用 Alamofire 请求

2024-01-04

最近我开始学习使用 swift 开发 iOS 应用程序,所以我对它很陌生。我想在 swift 中实现rest api 调用并发现我们可以使用URLRequest。所以我写了generic method调用所有类型(例如get, put, post) 的其余 api 如下。

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

我正在以下模型中解码服务器响应

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 request与错误处理类似401, 404等等我已经安装了Alamofire豆荚。有没有人开发过通用的Alamofire请求方法与解码和错误处理?

提前致谢 :)


git 链接:https://github.com/sahilmanchanda2/wrapper-class-for-alamofire https://github.com/sahilmanchanda2/wrapper-class-for-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 方法,可以选择以应用程序/JSON 格式或普通格式发送数据。通过这个类,您可以获得很大的灵活性,它会自动将响应转换为您的 Swift 对象。

看一下这个类的init方法它有:

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

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

  3. url = 这里你可以指定完整的url,如果你已经在Class中定义了baseurl,你可以将其留空。当您想要使用第三方提供的 REST 服务时,它会很方便。注意:如果您填写的是 url,那么下一个参数 service 应该为 nil

  4. service: services = 它是 NetworkClass 本身定义的枚举。这些作为端点。查看 init 方法,如果 url 为 nil 但服务不为 nil,那么它将附加在基本 url 的末尾以形成完整的 URL,将提供示例。

  5. method: HTTPMethod = 此处您可以指定请求应使用哪种 HTTP 方法。

  6. isJSONRequest = 默认设置为 true。如果你想发送普通请求,请将其设置为 false。

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

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

Usage:

假设我们有以下结构:

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

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

output:

Service: posts 
data: ["userId": 1, "body": "bar", "title": "foo"]
Post(userId: 1, id: 101, title: "foo", body: "bar")
  1. HTTP 400 请求

    NetworkCall(数据:[“电子邮件”:“peter@klaven”],网址:“https://reqres.in/api/login https://reqres.in/api/login", 方法: .post, isJSONRequest: false).executeQuery(){ (结果:结果)中 切换结果{ 案例.成功(让帖子): 打印(发布) 案例.失败(让错误): 打印(错误) } }

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

    NetworkCall(数据: ["用户名":"[电子邮件受保护] /cdn-cgi/l/email-protection”],标头:[“自定义标头键”:“自定义标头值”],网址:“https://httpbin.org/post https://httpbin.org/post", 方法: .post).executeQuery(){(结果: 结果) 中 切换结果{ 案例.成功(让数据): 打印(数据) 案例.失败(让错误): 打印(错误) } }

output:

Service: https://httpbin.org/post 
 data: ["username": "[email protected] /cdn-cgi/l/email-protection"]
{
  "args": {}, 
  "data": "{\"username\":\"[email protected] /cdn-cgi/l/email-protection\"}", 
  "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": "[email protected] /cdn-cgi/l/email-protection"
  }, 
  "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。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

iOS 使用 swift 创建通用 Alamofire 请求 的相关文章

随机推荐

  • 如何使用 vb.net 将超链接放入电子邮件正文

    我想做的是在 vb net 中添加一个到电子邮件正文的超链接 当我发送电子邮件时 我收到的是链接文本 以下是我到目前为止所做的事情 任何帮助将非常感激 Accepts two parameters the username and pass
  • 在 Neo4j 中创建一个具有除 ID 之外的唯一属性的节点

    我的项目基于 Spring boot Neo4j 我正在尝试创建一个新的权限节点 但不想重复权限 现在我有一个 UserRole 节点 它保存List
  • 使用基于 Java 的配置在服务器模式下设置 H2

    我有 spring XML 它使我能够使用以下配置以服务器模式启动 H2 数据库
  • 在 Android Studio 中设置 Hello, World 手表应用时遇到问题

    在 Android Studio 中部署包含的 Hello World 手表应用程序的可穿戴版本时 我收到此错误 失败 INSTALL FAILED OLDER SDK Update 从 Reddit 帖子中删除有关尝试 L 的黑客版本的详
  • 是否值得将distinct() 与collect(toSet()) 一起使用

    将流的元素收集到集合中时 同时指定是否有任何优点 或缺点 distinct 在流上 例如 return items stream map distinct collect toSet 考虑到该集合已经删除了重复项 这似乎是多余的 但是它是否
  • Javascript 检测 mouseup 上按住的控制键

    我查了很多资料 似乎找不到满意的解决方案 我希望有人能帮帮忙 当我使用 jQuery 时 我还编写了数千行 JavaScript 所以 纯 JavaScript 解决方案就可以了 我正在尝试确定控制键是否被物理按住mouseup事件 就是这
  • 如果 LINQ 中的列为空,如何忽略“where”和“order by”条件

    我有交易对象列表 并希望根据当前用户所在的视图按特定条件对它们进行排序 我遇到的问题是 为了在 where 子句中添加条件 首先我需要检查它是否为空以防止空指针异常 这会导致列为 null 的记录被过滤掉 我想将它们包含在列表底部 如果该列
  • 如何从“Android终端模拟器”调用Rebol解释器?

    我需要从以下位置调用 Rebol 语言解释器作为运行脚本文件的命令 例如rebol script name reb 所以我不是在寻找仅在 Rebol 中启动的 APK 应用程序REPL http en wikipedia org wiki
  • 使用 Google Closure 的 @typedef 标签

    Google 的 Closure 编译器有一个 typedef 标签 但是可以在代码中使用它们吗 我知道它会起作用 但是它会让人皱眉吗 所以这是我的类型 The plan object s typedef typedef Object Ty
  • C# ListView显示

    伙计们 我是在 C 中实现 ListView 的初学者 我在 c net 中的这些代码遇到问题 我无法弄清楚输出显示的控件中发生了什么 看来我忘记了在 ListView 控件的属性中赋予值的内容 第二列值必须出现在第一列上 这是我的代码 u
  • 字符串初始化的区别

    首先 如果这是一个非常基本的问题 我很抱歉 我只是想知道以下字符串定义之间的区别 String x hello String y new String hello 我知道在 java 中 String 是一个类 它既不是原语也不是包装器 如
  • 如何更改 WCF 服务引用的地址?

    我有一个在内部使用的应用程序 并使用 WCF 在客户端和服务器部分之间进行通信 但是它很快需要部署到服务器名称不同的站点 WCF 服务使用 netTcp 绑定作为 Windows 服务托管 目前 服务的地址是使用 Visual Studio
  • C# - 将文本与图像合并

    我编写了以下代码来合并 2 个图像 我的需求很简单 图像始终具有相同的尺寸 因此不需要定位 我可以稍后处理这个问题 我想知道的是 我可以修改它以将文本标签作为我的 imgFront 合并到图像 imgBack 上吗 最后返回的结果将是一个新
  • azure cdn purge不刷新缓存内容

    我有一个连接到 blob 存储的 Azure CDN Verizon 高级 基于此中的步骤 6 我制定了 2 条规则tutorial https blog lifeishao com 2017 05 24 serving your stat
  • 在键入并保留光标位置时强制 UITextField 小写

    用 Swift 2 编程 我有一个 UITextField 当用户在其中键入内容时 应在键入时自动转换为小写 因此不在表单验证之后 我已经走到这一步了 func textField textField UITextField shouldC
  • SQL 子句与表达式术语

    我与一位队友就以下主题进行了讨论 clause and expression可以互换使用 例如 调用代表某个变量的变量是否正确 常见 expression a b 例如 参与声明SELECT WHERE expression 一个子句 Ed
  • Linq to SQL for WebMatrix(网页)

    我想知道是否可以在基于 webmatrix 的网站 C Razor 等 中使用 Linq to SQL 我喜欢数据库搜索之类的东西 如果我可以使用 Linq to SQL 来实现这一点 那就太棒了 或者至少 将行作为数据集或其他内容返回 然
  • 如何限制列表对象模板侧而不是视图侧

    限制对象的方法之一是向这样的函数添加限制 def ten objects obj Model objects all 0 10 limit to 10 return objects obj 但是 如何在模板内而不是在视图内实现此目的 我知道
  • 什么是EOF!!在 bash 脚本中?

    有一个命令我不明白 custom command lt lt EOF 我想问什么是EOF 在 bash 脚本中 我确实用google找到了EOF 但是google会忽略 自动 所以我找不到 EOF 我知道文件结尾标记 但我不完全知道 的含义
  • iOS 使用 swift 创建通用 Alamofire 请求

    最近我开始学习使用 swift 开发 iOS 应用程序 所以我对它很陌生 我想在 swift 中实现rest api 调用并发现我们可以使用URLRequest 所以我写了generic method调用所有类型 例如get put pos