RestService 2.8.0

RestService 2.8.0

Ricardo Rauber Pereira 维护。



  • Ricardo Rauber Pereira

RestService - 为 iOS 项目提供的一个轻量级 REST 服务框架

Build Status CocoaPods Version License Platform

TL; DR

RestService 是基于 URLRequest 的一个轻量级 REST 服务,在上面添加了一些基础功能。看看这个

let service = RestService(host: "api.github.com")

let task = service.json(
    method: .get,
    path: "/users/ricardorauber/repos",
    responseType: [Repository].self) { response in
        switch response {
        case .success(let repositories):
            print("cool!", repositories)
        case .failure:
            print("oh no!")
        }
}

REST Service

大家好,感谢您来!您能到这里来是因为您正在寻找一种可以帮助您在应用中实现 HTTP 请求的工具,对吗?这正是这个框架所做的事情,希望它会成功地融入您的项目中。您可能会想,为什么还需要另一个服务,与像 Alamofire 这样的优秀框架相比有什么优势呢?嗯,当然,市面上有很多解决方案,它们确实很棒。唯一的问题是它们提供了太多的可能性,而我只想建立一个简单、安全且易于使用的框架。让我向您展示它是如何工作的,然后您将决定它是否是您的好选择。

设置

CocoaPods

如果您使用CocoaPods,请将其添加到您的Podfile中,并运行pod install

target 'Your target name' do
    pod 'RestService', '~> 2.3'
end

手动安装

如果您想手动将其添加到项目中,不使用包管理器,只需将所有文件从Classes文件夹复制到您的项目中。

使用方法

创建服务

为了创建服务实例,您只需导入框架,并使用您的主机实例化RestService。比如说,您的主机是https://server.com/

import RestService
let service = RestService(host: "server.com")

这样就会创建一个使用https方案的服务。但如果您需要使用其他方案,您可以为此服务添加更多信息

import RestService
let service = RestService(
    session: URLSession.shared,
    encoder: JSONEncoder(),
    decoder: JSONDecoder(),
    scheme: .http, 
    host: "localhost", 
    port: 3000,
    basePath: "/api"
)

这样它将创建一个针对https://:3000/api的服务。

请注意,所有这些属性以后都可以修改。

设置basePath参数为所有请求设置基本路径,所以如果您请求/users路径,例如,它将发送到https://:3000/api/users

您也可以使用一个URL值或甚至是String作为URL

import RestService

let urlService = RestService(url: URL(string: "https://server.com:3000")!)
let stringService = RestService(url: "https://server.com:3000")

发送简单的JSON请求

现在您已创建服务,是时候进行一些请求了。让我们从一个非常简单的GET请求开始,针对api/users端点。

service.json(
    method: .get,
    path: "/api/users") { response in
    
    switch response {
    case .success:
        print("success!")
    case .failure:
        print("failure")
    }
}

看这个?很简单!

使用参数发送 JSON 请求

现在让我们用参数发送一个请求,但在做之前,我们需要明确一下这些参数。当您发送 GETHEADDELETE 请求时,您将通过查询字符串发送参数,但对于所有其他方法,您将在请求体中发送参数。据此,RestService 将根据您为该请求选择的方法设置正确的属性。

这里还有一点是,您要决定是否要使用简单的字典或 Encodable 对象作为参数。我个人更喜欢 Encodable 对象,因为编译器将会对您的参数名称中的拼写错误发出警告,而且编写 API 逻辑会更好。

所以,让我们使用字典作为参数发送一个 GET 请求

let parameters: [String: Any] = [
    "username": "john",
    "limit": 10,
    "offset": 0
]

service.json(
    method: .get,
    path: "/api/users",
    parameters: parameters) { response in
    
    switch response {
    case .success:
        print("success!")
    case .failure:
        print("failure")
    }
}

发生了什么事?嗯,它确实创建了一个请求,这个网址没有请求体:https://server.com/api/users?username=john&limit=10&offset=0

关于 POST 请求呢?让我们用 Encodable 对象来演示一下!

struct CreateUserRequest: Codable {
    let username: String
    let password: String
}

let parameters = CreateUserRequest(username: "john", password: "safepassword")

service.json(
    method: .post,
    path: "/api/users",
    parameters: parameters) { response in
    
    switch response {
    case .success:
        print("success!")
    case .failure:
        print("failure")
    }
}

这样,它创建了一个 targeted POST 请求,网址为 https://server.com/api/users,并且带有了这个 JSON 请求体

{
    "username": "john",
    "password": "safepassword"
}

厉害吧?

使用参数发送 FORM/DATA 请求

如果您需要以 form/data 格式发送请求,您可以使用一些 FormDataParameters 很容易地发送它。您可以创建一个 FileFormDataParameter 或一个 TextFormDataParameter

所以,让我们用一些参数发送一个 POST 请求

let parameters: [FormDataParameter] = [
    TextFormDataParameter(name: "id", value: "10"),
    TextFormDataParameter(name: "email", value: "[email protected]"),
    FileFormDataParameter(name: "text", filename: "text.txt", contentType: "text/plain", data: textData),
    FileFormDataParameter(name: "image", filename: "thumb.png", contentType: "image/png", data: imageData)
]

service.formData(
    method: .post,
    path: "/api/userdata",
    parameters: parameters) { response in
    
    switch response {
    case .success:
        print("success!")
    case .failure:
        print("failure")
    }
}

使用参数发送表单 URL 编码请求

这是另一种类似于 x-www-form-urlencoded 的请求,就像 JSON 请求一样容易。

let parameters: [String: Any] = [
    "username": "john",
    "password": "12345"
]

service.formUrlEncoded(
    method: .post,
    path: "/api/login",
    parameters: parameters) { response in
    
    switch response {
    case .success:
        print("success!")
    case .failure:
        print("failure")
    }
}

使用数据体发送简单请求

好的,你未使用任何列出的请求类型,但你仍然想使用 RestService 发送请求?当然,这非常简单,只需要将正文发送为 Data

service.request(
    method: .post,
    path: "/api/upload",
    body: body) { response in
    
    switch response {
    case .success:
        print("success!")
    case .failure:
        print("failure")
    }
}

使用拦截器进行请求

拦截器是修改请求(在将其发送到服务器之前)的绝佳方式。这是一个添加类似身份验证令牌等头部的绝佳机会。让我们看看它是如何工作的!

struct TokenInterceptor: RequestInterceptor {
    func adapt(request: URLRequest) -> URLRequest {
        var request = request
        request.addValue("Bearer gahsjdGJSgdsajagA", forHTTPHeaderField: "Authorization")
        return request
    }
}

let interceptor = TokenInterceptor()

service.json(
    method: .get,
    path: "/api/me",
    interceptor: interceptor) { response in
    
    switch response {
    case .success:
        print("success!")
    case .failure:
        print("failure")
    }
}

这将在执行前将令牌添加到请求中。拦截器可以重复使用,非常适合添加服务器所需的所有东西。

如果你需要添加多个拦截器,可以使用 RestInterceptorGroup

struct APIKeyInterceptor: RequestInterceptor {
    func adapt(request: URLRequest) -> URLRequest {
        var request = request
        request.addValue("12345", forHTTPHeaderField: "X-API-KEY")
        return request
    }
}
    
struct TokenInterceptor: RequestInterceptor {
    func adapt(request: URLRequest) -> URLRequest {
        var request = request
        request.addValue("Bearer gahsjdGJSgdsajagA", forHTTPHeaderField: "Authorization")
        return request
    }
}
    
let interceptor = GroupInterceptor(interceptors: [
    APIKeyInterceptor(),
    TokenInterceptor()
])

service.json(
    method: .get,
    path: "/api/me",
    interceptor: interceptor) { response in
    
    switch response {
    case .success:
        print("success!")
    case .failure:
        print("failure")
    }
}

处理来自服务器的响应

很多人遇到的问题是服务器响应。类型转换,序列化,解码……选择无限。我尝试通过这个框架实现的是,以你需要的数据形式获得漂亮的响应。

有 4 种响应可能性

无正文

如果你只需发送请求,并且不期望获得任何对象作为响应,那么你只需像之前看到的那样调用即可

service.json(
    method: .get,
    path: "/api/me") { response in
    
    switch response {
    case .success:
        print("success!")
    case .failure:
        print("failure")
    }
}

带有响应类型

现在假设你需要将响应正文转换为 Codable 对象,当请求成功时

struct Person: Codable {
    let id: Int
    let name: String
}

service.json(
    method: .get,
    path: "/api/me",
    responseType: Person.self) { response in
    
    switch response {
    case .success(let person):
        print("success!", person)
    case .failure:
        print("failure")
    }
}

来自服务器的自定义错误

如果你的Web服务以自定义方式返回错误,比如返回一个包含错误详情的json对象,你可以使用另一个参数来将响应解码为Codable对象,如下所示

struct ServerError: Codable {
    let code: Int
    let message: String
}

service.json(
    method: .get,
    path: "/api/me",
    customError: ServerError.self) { response in
    
    switch response {
    case .success:
        print("success!")
    case .customError(let error):
        print("server error", error)
    case .failure:
        print("failure")
    }
}

全部包含:响应类型 + 自定义来自服务器的错误

最后,你可以将所有内容放在一起,成功的响应类型、来自服务器的自定义错误和通用的失败响应

struct Person: Codable {
    let id: Int
    let name: String
}
    
struct ServerError: Codable {
    let code: Int
    let message: String
}

service.json(
    method: .get,
    path: "/api/me",
    responseType: Person.self,
    customError: ServerError.self) { response in
    
    switch response {
    case .success(let person):
        print("success!", person)
    case .customError(let error):
        print("server error", error)
    case .failure:
        print("failure")
    }
}

处理任务的进度

很酷的事情是,使用RestService你可以处理任何类型任务(而不仅仅是JSON)的进度,例如更新UI

service.json(
    method: .get,
    path: "/api/me",
    progress: { value in
        print("progress:", value)
    },
    completion: { response in
    
        switch response {
        case .success:
            print("success!")
        case .failure:
            print("failure")
        }
    }
)

调试/日志

最后但同样重要的是,作为开发者,我们经常需要调试一些请求,并且通常需要付出一定的努力来使其变得漂亮,因此我做了一些酷的事情让您的生命变得更加简单

let service = RestService(debug: true, host: "api.github.com")

只需在创建服务时添加debug参数并将其设置为true,您将看到所有请求都在Xcode控制台中记录。很棒,对吧?但这还不是全部,您可以将单个请求的调试设置为

service.json(
    debug: true,
    method: .get,
    path: "/api/me",
    completion: { response in
    
        switch response {
        case .success:
            print("success!")
        case .failure:
            print("failure")
        }
    }
)

它看起来是什么样子?以下是一个小样本

==============================================
 ⚪️ REQUEST: https://api.github.com/users/ricardorauber/repos 

> Headers:
[
    "Content-Type": "application/json; charset=utf-8"
] 

> Body:
nil 

 🟢 RESPONSE: 200 

> Headers:
[
    "referrer-policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
    "x-github-media-type": "github.v3; format=json",
    "Status": "200 OK",
    ...
]

> Body:
[{"id":253579430,"node_id":"MDEwOlJlcG9zaXRvcnkyNTM1Nzk0MzA="}]

------------------------------

谢谢👍

本框架的创建得益于这些优秀的人们

欢迎反馈

如果您发现任何问题,卡住了,或者只是想聊天,请随意创建一个问题。我们将很高兴为您提供帮助。

许可证

RestService是在MIT许可证下发布的。